View Javadoc
1   package com.buckosoft.PicMan.service.support;
2   
3   import java.io.IOException;
4   import java.lang.reflect.InvocationTargetException;
5   import java.util.HashSet;
6   import java.util.Map;
7   
8   import com.fasterxml.jackson.core.JsonParser;
9   import com.fasterxml.jackson.core.JsonProcessingException;
10  import com.fasterxml.jackson.core.JsonToken;
11  import com.fasterxml.jackson.databind.AnnotationIntrospector;
12  import com.fasterxml.jackson.databind.BeanProperty;
13  import com.fasterxml.jackson.databind.DeserializationContext;
14  import com.fasterxml.jackson.databind.JavaType;
15  import com.fasterxml.jackson.databind.JsonDeserializer;
16  import com.fasterxml.jackson.databind.JsonMappingException;
17  import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
18  import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
19  import com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer;
20  import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
21  import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
22  import com.fasterxml.jackson.databind.deser.ValueInstantiator;
23  import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
24  import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
25  import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
26  import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
27  import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
28  import com.fasterxml.jackson.databind.util.ArrayBuilders;
29  
30  /**
31   * Basic serializer that can take Json "Object" structure and construct a
32   * {@link java.util.Map} instance, with typed contents.
33   * <p>
34   * Note: for untyped content (one indicated by passing Object.class as the
35   * type), {@link UntypedObjectDeserializer} is used instead. It can also
36   * construct {@link java.util.Map}s, but not with specific POJO types, only
37   * other containers and primitives/wrappers.
38   */
39  @JacksonStdImpl
40  public class KeyValueMapDeserializer extends
41  		ContainerDeserializerBase<Map<Object, Object>> implements
42  		ContextualDeserializer, ResolvableDeserializer, MapLabels {
43  	// // Configuration: typing, deserializers
44  
45  	private static final long serialVersionUID = 6589120424431005028L;
46  
47  	protected final JavaType _mapType;
48  
49  	/**
50  	 * Key deserializer to use; either passed via constructor (when indicated by
51  	 * annotations), or resolved when {@link #resolve} is called;
52  	 */
53  	protected final JsonDeserializer<Object> _keyDeserializer;
54  
55  	/**
56  	 * Flag set to indicate that the key type is {@link java.lang.String} (or
57  	 * {@link java.lang.Object}, for which String is acceptable), <b>and</b>
58  	 * that the default Jackson key deserializer would be used. If both are
59  	 * true, can optimize handling.
60  	 */
61  	protected boolean _standardStringKey;
62  
63  	/**
64  	 * Value deserializer.
65  	 */
66  	protected final JsonDeserializer<Object> _valueDeserializer;
67  
68  	/**
69  	 * If value instances have polymorphic type information, this is the type
70  	 * deserializer that can handle it
71  	 */
72  	protected final TypeDeserializer _valueTypeDeserializer;
73  
74  	// // Instance construction settings:
75  
76  	protected final ValueInstantiator _valueInstantiator;
77  
78  	protected final boolean _hasDefaultCreator;
79  
80  	/**
81  	 * Deserializer that is used iff delegate-based creator is to be used for
82  	 * deserializing from JSON Object.
83  	 */
84  	protected JsonDeserializer<Object> _delegateDeserializer;
85  
86  	/**
87  	 * If the Map is to be instantiated using non-default constructor or factory
88  	 * method that takes one or more named properties as argument(s), this
89  	 * creator is used for instantiation.
90  	 */
91  	protected PropertyBasedCreator _propertyBasedCreator;
92  
93  	// // Any properties to ignore if seen?
94  
95  	protected HashSet<String> _ignorableProperties;
96  
97  	/*
98  	 * /********************************************************** /* Life-cycle
99  	 * /**********************************************************
100 	 */
101 
102 	@SuppressWarnings("deprecation")
103 	public KeyValueMapDeserializer(JavaType mapType,
104 			ValueInstantiator valueInstantiator,
105 			JsonDeserializer<Object> keyDeser,
106 			JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser) {
107 		super(Map.class);
108 		_mapType = mapType;
109 		_keyDeserializer = keyDeser;
110 		_valueDeserializer = valueDeser;
111 		_valueTypeDeserializer = valueTypeDeser;
112 		_valueInstantiator = valueInstantiator;
113 		_hasDefaultCreator = false;//valueInstantiator.canCreateUsingDefault();
114 		_delegateDeserializer = null;
115 		_propertyBasedCreator = null;
116 		_standardStringKey = _isStdKeyDeser(mapType, keyDeser);
117 	}
118 
119 	/**
120 	 * Copy-constructor that can be used by sub-classes to allow copy-on-write
121 	 * styling copying of settings of an existing instance.
122 	 */
123 	@SuppressWarnings("deprecation")
124 	protected KeyValueMapDeserializer(KeyValueMapDeserializer src) {
125 		super(src._valueClass);
126 		_mapType = src._mapType;
127 		_keyDeserializer = src._keyDeserializer;
128 		_valueDeserializer = src._valueDeserializer;
129 		_valueTypeDeserializer = src._valueTypeDeserializer;
130 		_valueInstantiator = src._valueInstantiator;
131 		_propertyBasedCreator = src._propertyBasedCreator;
132 		_delegateDeserializer = src._delegateDeserializer;
133 		_hasDefaultCreator = src._hasDefaultCreator;
134 		// should we make a copy here?
135 		_ignorableProperties = src._ignorableProperties;
136 
137 		_standardStringKey = src._standardStringKey;
138 	}
139 
140 	@SuppressWarnings("deprecation")
141 	protected KeyValueMapDeserializer(KeyValueMapDeserializer src,
142 			ValueInstantiator valueInstantiator,
143 			JsonDeserializer<Object> keyDeser,
144 			JsonDeserializer<Object> valueDeser,
145 			TypeDeserializer valueTypeDeser, HashSet<String> ignorable) {
146 		super(src._valueClass);
147 		_mapType = src._mapType;
148 		_keyDeserializer = keyDeser;
149 		_valueDeserializer = valueDeser;
150 		_valueTypeDeserializer = valueTypeDeser;
151 		_valueInstantiator = valueInstantiator;
152 		_propertyBasedCreator = src._propertyBasedCreator;
153 		_delegateDeserializer = src._delegateDeserializer;
154 		_hasDefaultCreator = src._hasDefaultCreator;
155 		_ignorableProperties = ignorable;
156 
157 		_standardStringKey = _isStdKeyDeser(_mapType, keyDeser);
158 	}
159 
160 	/**
161 	 * Fluent factory method used to create a copy with slightly different
162 	 * settings. When sub-classing, MUST be overridden.
163 	 */
164 	@SuppressWarnings("unchecked")
165 	protected KeyValueMapDeserializer withResolved(
166 			JsonDeserializer<?> keyDeser, TypeDeserializer valueTypeDeser,
167 			JsonDeserializer<?> valueDeser, ValueInstantiator vi, HashSet<String> ignorable) {
168 
169 		if ((_keyDeserializer == keyDeser)
170 				&& (_valueDeserializer == valueDeser)
171 				&& (_valueTypeDeserializer == valueTypeDeser)
172 				&& (_ignorableProperties == ignorable)
173 				&& (_valueInstantiator == vi)) {
174 			return this;
175 		}
176 		return new KeyValueMapDeserializer(this,
177 				vi,
178 				(JsonDeserializer<Object>) keyDeser,
179 				(JsonDeserializer<Object>) valueDeser, valueTypeDeser,
180 				ignorable);
181 	}
182 
183 	/**
184 	 * Helper method used to check whether we can just use the default key
185 	 * deserialization, where JSON String becomes Java String.
186 	 */
187 	protected final boolean _isStdKeyDeser(JavaType mapType,
188 			JsonDeserializer<Object> keyDeser) {
189 		if (keyDeser == null) {
190 			return true;
191 		}
192 		JavaType keyType = mapType.getKeyType();
193 		if (keyType == null) { // assumed to be Object
194 			return true;
195 		}
196 		Class<?> rawKeyType = keyType.getRawClass();
197 		return ((rawKeyType == String.class || rawKeyType == Object.class) && _isDefaultKeyDeserializer(keyDeser));
198 	}
199 
200 	protected boolean _isDefaultKeyDeserializer(
201 			JsonDeserializer<Object> keyDeser) {
202 		return (keyDeser != null && keyDeser.getClass().getAnnotation(
203 				JacksonStdImpl.class) != null);
204 	}
205 
206 	public void setIgnorableProperties(String[] ignorable) {
207 		_ignorableProperties = (ignorable == null || ignorable.length == 0) ? null
208 				: ArrayBuilders.arrayToSet(ignorable);
209 	}
210 
211 	/*
212 	 * /********************************************************** /*
213 	 * Validation, post-processing (ResolvableDeserializer)
214 	 * /**********************************************************
215 	 */
216 
217 	// @Override
218 	public void resolve(DeserializationContext ctxt)
219 			throws JsonMappingException {
220 		// May need to resolve types for delegate- and/or property-based
221 		// creators:
222 		final ValueInstantiator valueInstantiator = ctxt.getFactory().findValueInstantiator(ctxt, ctxt.getConfig().introspectForCreation(_mapType));
223 		
224 		if (valueInstantiator.canCreateUsingDelegate()) {
225 			JavaType delegateType = valueInstantiator.getDelegateType(ctxt
226 					.getConfig());
227 			if (delegateType == null) {
228 				throw new IllegalArgumentException(
229 						"Invalid delegate-creator definition for "
230 								+ _mapType
231 								+ ": value instantiator ("
232 								+ valueInstantiator.getClass().getName()
233 								+ ") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'");
234 			}
235 			/*
236 			 * Theoretically should be able to get CreatorProperty for delegate
237 			 * parameter to pass; but things get tricky because DelegateCreator
238 			 * may contain injectable values. So, for now, let's pass nothing.
239 			 */
240 			_delegateDeserializer = findDeserializer(ctxt, delegateType, null);
241 		}
242 		if (valueInstantiator.canCreateFromObjectWith()) {
243 			SettableBeanProperty[] creatorProps = valueInstantiator
244 					.getFromObjectArguments(ctxt.getConfig());
245 			_propertyBasedCreator = PropertyBasedCreator.construct(ctxt,
246 					valueInstantiator, creatorProps);
247 		}
248 		_standardStringKey = _isStdKeyDeser(_mapType, _keyDeserializer);
249 	}
250 
251 	/**
252 	 * Method called to finalize setup of this deserializer, when it is known
253 	 * for which property deserializer is needed for.
254 	 */
255 	// @Override
256 	public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
257 			BeanProperty property) throws JsonMappingException {
258 		JsonDeserializer<?> kd = _keyDeserializer;
259 		if (kd == null) {
260 			kd = ctxt.findContextualValueDeserializer(_mapType.getKeyType(),
261 					property);
262 		} else {
263 			if (kd instanceof ContextualKeyDeserializer) {
264 				kd = ((ContextualDeserializer) kd).createContextual(ctxt,
265 						property);
266 			}
267 		}
268 		JsonDeserializer<?> vd = _valueDeserializer;
269 		if (vd == null) {
270 			vd = ctxt.findContextualValueDeserializer(
271 					_mapType.getContentType(), property);
272 		} else { // if directly assigned, probably not yet contextual, so:
273 			if (vd instanceof ContextualDeserializer) {
274 				vd = ((ContextualDeserializer) vd).createContextual(ctxt,
275 						property);
276 			}
277 		}
278 		TypeDeserializer vtd = _valueTypeDeserializer;
279 		if (vtd != null) {
280 			vtd = vtd.forProperty(property);
281 		}
282 		HashSet<String> ignored = _ignorableProperties;
283 		AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
284 		if (intr != null && property != null) {
285 			String[] moreToIgnore = intr.findPropertiesToIgnore(property
286 					.getMember());
287 			if (moreToIgnore != null) {
288 				ignored = (ignored == null) ? new HashSet<String>()
289 						: new HashSet<String>(ignored);
290 				for (String str : moreToIgnore) {
291 					ignored.add(str);
292 				}
293 			}
294 		}
295 		
296 		ValueInstantiator vi = _valueInstantiator;
297 		if (vi == null) {
298 			vi = ctxt.getFactory().findValueInstantiator(ctxt, ctxt.getConfig().introspectForCreation(_mapType));
299 		}
300 		return withResolved(kd, vtd, vd, vi, ignored);
301 	}
302 
303 	/*
304 	 * /********************************************************** /*
305 	 * ContainerDeserializerBase API
306 	 * /**********************************************************
307 	 */
308 
309 	@Override
310 	public JavaType getContentType() {
311 		return _mapType.getContentType();
312 	}
313 
314 	@Override
315 	public JsonDeserializer<Object> getContentDeserializer() {
316 		return _valueDeserializer;
317 	}
318 
319 	/*
320 	 * /********************************************************** /*
321 	 * JsonDeserializer API
322 	 * /**********************************************************
323 	 */
324 
325 	@Override
326 	@SuppressWarnings("unchecked")
327 	public Map<Object, Object> deserialize(JsonParser jp,
328 			DeserializationContext ctxt) throws IOException,
329 			JsonProcessingException {
330 		
331 		if (_propertyBasedCreator != null) {
332 			return _deserializeUsingCreator(jp, ctxt);
333 		}
334 		if (_delegateDeserializer != null) {
335 			return (Map<Object, Object>) _valueInstantiator
336 					.createUsingDelegate(ctxt,
337 							_delegateDeserializer.deserialize(jp, ctxt));
338 		}
339 		if (!_hasDefaultCreator && !_valueInstantiator.canCreateUsingDefault()) {
340 			throw ctxt.instantiationException(getMapClass(),
341 					"No default constructor found");
342 		}
343 		// Ok: must point to START_ARRAY, START_OBJECT or END_ARRAY
344 		JsonToken t = jp.getCurrentToken();
345 		if (t != JsonToken.START_ARRAY && t != JsonToken.START_OBJECT
346 				&& t != JsonToken.END_ARRAY) {
347 			// [JACKSON-620] (empty) String may be ok however:
348 			if (t == JsonToken.VALUE_STRING) {
349 				return (Map<Object, Object>) _valueInstantiator
350 						.createFromString(ctxt, jp.getText());
351 			}
352 			throw ctxt.mappingException(getMapClass());
353 		}
354 		final Map<Object, Object> result = (Map<Object, Object>) _valueInstantiator
355 				.createUsingDefault(ctxt);
356 		if (_standardStringKey) {
357 			_readAndBindStringMap(jp, ctxt, result);
358 			return result;
359 		}
360 		_readAndBind(jp, ctxt, result);
361 		return result;
362 	}
363 
364 	@Override
365 	public Map<Object, Object> deserialize(JsonParser jp,
366 			DeserializationContext ctxt, Map<Object, Object> result)
367 			throws IOException, JsonProcessingException {
368 		// Ok: must point to START_ARRAY or START_OBJECT
369 		JsonToken t = jp.getCurrentToken();
370 		if (t != JsonToken.START_ARRAY && t != JsonToken.START_OBJECT) {
371 			throw ctxt.mappingException(getMapClass());
372 		}
373 		if (_standardStringKey) {
374 			_readAndBindStringMap(jp, ctxt, result);
375 			return result;
376 		}
377 		_readAndBind(jp, ctxt, result);
378 		return result;
379 	}
380 
381 	@Override
382 	public Object deserializeWithType(JsonParser jp,
383 			DeserializationContext ctxt, TypeDeserializer typeDeserializer)
384 			throws IOException, JsonProcessingException {
385 		// In future could check current token... for now this should be enough:
386 		return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
387 	}
388 
389 	/*
390 	 * /********************************************************** /* Other
391 	 * public accessors
392 	 * /**********************************************************
393 	 */
394 
395 	@SuppressWarnings("unchecked")
396 	public final Class<?> getMapClass() {
397 		return (Class<Map<Object, Object>>) _mapType.getRawClass();
398 	}
399 
400 	@Override
401 	public JavaType getValueType() {
402 		return _mapType;
403 	}
404 
405 	/*
406 	 * /********************************************************** /* Internal
407 	 * methods /**********************************************************
408 	 */
409 
410 	protected final void _readAndBind(JsonParser jp,
411 			DeserializationContext ctxt, Map<Object, Object> result)
412 			throws IOException, JsonProcessingException {
413 		JsonToken t = jp.getCurrentToken();
414 		if (t == JsonToken.START_ARRAY) {
415 			t = jp.nextToken();
416 		}
417 		final JsonDeserializer<Object> keyDes = _keyDeserializer;
418 		final JsonDeserializer<Object> valueDes = _valueDeserializer;
419 		final TypeDeserializer typeDeser = _valueTypeDeserializer;
420 		for (; t == JsonToken.START_OBJECT; t = jp.nextToken()) {
421 			Object key = null, value = null;
422 			
423 			for (t = jp.nextToken(); t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
424 				// Must point to field name
425 				String fieldName = jp.getCurrentName();
426 				t = jp.nextToken();
427 				if (_ignorableProperties != null
428 						&& _ignorableProperties.contains(fieldName)) {
429 					jp.skipChildren();
430 					continue;
431 				}
432 
433 				if (MAP_KEY_NAME.equals(fieldName)) {
434 					if (t == JsonToken.VALUE_NULL) {
435 						// handle null keys
436 						key = null;
437 					} else {
438 						key = keyDes.deserialize(jp, ctxt);
439 					}
440 				} else if (MAP_VALUE_NAME.equals(fieldName)) {
441 					// Note: must handle null explicitly here; value
442 					// deserializers won't
443 					if (t == JsonToken.VALUE_NULL) {
444 						value = null;
445 					} else if (typeDeser == null) {
446 						value = valueDes.deserialize(jp, ctxt);
447 					} else {
448 						value = valueDes.deserializeWithType(jp, ctxt,
449 								typeDeser);
450 					}
451 				}
452 				/*
453 				 * !!! 23-Dec-2008, tatu: should there be an option to verify
454 				 * that there are no duplicate field names? (and/or what to do,
455 				 * keep-first or keep-last)
456 				 */
457 				if (key != null) {
458 					result.put(key, value);
459 				}
460 			}
461 		}
462 	}
463 
464 	/**
465 	 * Optimized method used when keys can be deserialized as plain old
466 	 * {@link java.lang.String}s, and there is no custom deserialized specified.
467 	 */
468 	protected final void _readAndBindStringMap(JsonParser jp,
469 			DeserializationContext ctxt, Map<Object, Object> result)
470 			throws IOException, JsonProcessingException {
471 		JsonToken t = jp.getCurrentToken();
472 		if (t == JsonToken.START_OBJECT) {
473 			t = jp.nextToken();
474 		}
475 		final JsonDeserializer<Object> valueDes = _valueDeserializer;
476 		final TypeDeserializer typeDeser = _valueTypeDeserializer;
477 		for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
478 			// Must point to field name
479 			String fieldName = jp.getCurrentName();
480 			// And then the value...
481 			t = jp.nextToken();
482 			if (_ignorableProperties != null
483 					&& _ignorableProperties.contains(fieldName)) {
484 				jp.skipChildren();
485 				continue;
486 			}
487 			// Note: must handle null explicitly here; value deserializers won't
488 			Object value;
489 			if (t == JsonToken.VALUE_NULL) {
490 				value = null;
491 			} else if (typeDeser == null) {
492 				value = valueDes.deserialize(jp, ctxt);
493 			} else {
494 				value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
495 			}
496 			result.put(fieldName, value);
497 		}
498 	}
499 
500 	@SuppressWarnings("unchecked")
501 	public Map<Object, Object> _deserializeUsingCreator(JsonParser jp,
502 			DeserializationContext ctxt) throws IOException,
503 			JsonProcessingException {
504 		final PropertyBasedCreator creator = _propertyBasedCreator;
505 		// null -> no ObjectIdReader for Maps (yet?)
506 		PropertyValueBuffer buffer = creator.startBuilding(jp, ctxt, null);
507 
508 		JsonToken t = jp.getCurrentToken();
509 		if (t == JsonToken.START_OBJECT) {
510 			t = jp.nextToken();
511 		}
512 		final JsonDeserializer<Object> valueDes = _valueDeserializer;
513 		final TypeDeserializer typeDeser = _valueTypeDeserializer;
514 		for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
515 			String propName = jp.getCurrentName();
516 			t = jp.nextToken(); // to get to value
517 			if (_ignorableProperties != null
518 					&& _ignorableProperties.contains(propName)) {
519 				jp.skipChildren(); // and skip it (in case of array/object)
520 				continue;
521 			}
522 			// creator property?
523 			SettableBeanProperty prop = creator.findCreatorProperty(propName);
524 			if (prop != null) {
525 				// Last property to set?
526 				Object value = prop.deserialize(jp, ctxt);
527 				if (buffer.assignParameter(prop.getCreatorIndex(), value)) {
528 					jp.nextToken();
529 					Map<Object, Object> result;
530 					try {
531 						result = (Map<Object, Object>) creator.build(ctxt,
532 								buffer);
533 					} catch (Exception e) {
534 						wrapAndThrow(e, _mapType.getRawClass());
535 						return null;
536 					}
537 					_readAndBind(jp, ctxt, result);
538 					return result;
539 				}
540 				continue;
541 			}
542 			Object key = _keyDeserializer.deserialize(jp, ctxt);
543 			Object value;
544 			if (t == JsonToken.VALUE_NULL) {
545 				value = null;
546 			} else if (typeDeser == null) {
547 				value = valueDes.deserialize(jp, ctxt);
548 			} else {
549 				value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
550 			}
551 			buffer.bufferMapProperty(key, value);
552 		}
553 		// end of JSON object?
554 		// if so, can just construct and leave...
555 		try {
556 			return (Map<Object, Object>) creator.build(ctxt, buffer);
557 		} catch (Exception e) {
558 			wrapAndThrow(e, _mapType.getRawClass());
559 			return null;
560 		}
561 	}
562 
563 	// note: copied form BeanDeserializer; should try to share somehow...
564 	protected void wrapAndThrow(Throwable t, Object ref) throws IOException {
565 		// to handle StackOverflow:
566 		while (t instanceof InvocationTargetException && t.getCause() != null) {
567 			t = t.getCause();
568 		}
569 		// Errors and "plain" IOExceptions to be passed as is
570 		if (t instanceof Error) {
571 			throw (Error) t;
572 		}
573 		// ... except for mapping exceptions
574 		if (t instanceof IOException && !(t instanceof JsonMappingException)) {
575 			throw (IOException) t;
576 		}
577 		throw JsonMappingException.wrapWithPath(t, ref, null);
578 	}
579 }