/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.web.json;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.validation.BindingResult;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* Configure jackson seralizers for a bean.
* <p/>
* If it's possible, register a Serializer based on {@link ConversionService}
* registered conversions.
* <p/>
* If a property already has a serialized configured (normally by
* {@link JsonSerialize#using()}) this will be use.
* <p/>
* Also, if {@link JsonSerialize#as()} is set, it tries to use this class as
* serialize target class.
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
* @since TODO: Class version
*/
public class ConversionServiceBeanSerializerModifier extends
BeanSerializerModifier {
private final ConversionService conversionService;
private final BindingResultSerializer bindingResultSerializer;
public ConversionServiceBeanSerializerModifier(
ConversionService conversionService) {
super();
this.conversionService = conversionService;
this.bindingResultSerializer = new BindingResultSerializer();
}
@Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
// We need the BeanPropertyDefinition to get the related Field
List<BeanPropertyDefinition> properties = beanDesc.findProperties();
Map<String, BeanPropertyDefinition> propertyDefMap = new HashMap<String, BeanPropertyDefinition>();
for (BeanPropertyDefinition property : properties) {
propertyDefMap.put(property.getName(), property);
}
// iterate over bean's properties to configure serializers
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
Class<?> propertyType = beanPropertyWriter.getPropertyType();
if (beanPropertyWriter.hasSerializer()) {
continue;
}
// For conversion between collection, array, and map types,
// ConversionService.canConvert() method will return 'true'
// but better to delegate in default Jackson property writer for
// right start and ends markers serialization and iteration
if (propertyType.isArray()
|| Collection.class.isAssignableFrom(propertyType)
|| Map.class.isAssignableFrom(propertyType)) {
// Don't set ConversionService serializer, let Jackson
// use default Collection serializer
continue;
}
else if (BindingResult.class.isAssignableFrom(propertyType)) {
// Use BindingResultSerializer
beanPropertyWriter.assignSerializer(bindingResultSerializer);
}
else {
// ConversionService uses value Class plus related Field
// annotations to be able to select the right converter,
// so we must get/ the Field annotations for success
// formatting
BeanPropertyDefinition propertyDef = propertyDefMap
.get(beanPropertyWriter.getName());
AnnotatedField annotatedField = propertyDef.getField();
if (annotatedField == null) {
continue;
}
AnnotatedElement annotatedEl = annotatedField.getAnnotated();
// Field contains info about Annotations, info that
// ConversionService uses for success formatting, use it if
// available. Otherwise use the class of given value.
TypeDescriptor sourceType = annotatedEl != null ? new TypeDescriptor(
(Field) annotatedEl) : TypeDescriptor
.valueOf(propertyType);
TypeDescriptor targetType = TypeDescriptor
.valueOf(String.class);
if (beanPropertyWriter.getSerializationType() != null) {
targetType = TypeDescriptor.valueOf(beanPropertyWriter
.getSerializationType().getRawClass());
}
if (ObjectUtils.equals(sourceType, targetType)) {
// No conversion needed
continue;
}
else if (sourceType.getObjectType() == Object.class
&& targetType.getObjectType() == String.class
&& beanPropertyWriter.getSerializationType() == null) {
// Can't determine source type and no target type has been
// configure. Delegate on jackson.
continue;
}
// All other converters must be set in ConversionService
if (this.conversionService.canConvert(sourceType, targetType)) {
// We must create BeanPropertyWriter own Serializer that
// has knowledge about the Field related to that
// BeanPropertyWriter in order to have access to
// Field Annotations for success serialization
JsonSerializer<Object> jsonSerializer = new ConversionServicePropertySerializer(
this.conversionService, sourceType, targetType);
beanPropertyWriter.assignSerializer(jsonSerializer);
}
// If no converter set, use default Jackson property writer
else {
continue;
}
}
}
return beanProperties;
}
}