/* * 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.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.validation.Valid; import org.springframework.beans.BeanUtils; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.validation.BindingResult; import org.springframework.validation.Validator; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; /** * Overrides {@link MappingJackson2HttpMessageConverter} to customize the call * to {@link ObjectMapper}. * <p/> * Before call to {@link ObjectMapper#readValue(java.io.InputStream, Class)} * this class creates a {@link ServletRequestDataBinder} and put it to current * Thread in order to be used by the {@link DataBinderDeserializer}. * <p/> * Moreover if incoming JSON content is an array, this class creates a * {@link DataBinderList} and setup it as DataBinder target object for success * deserialization and binding errors storing. * * @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 DataBinderMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { private final ConversionService conversionService; private final Validator validator; /** * Default constructor * * @param cs * @param validator */ public DataBinderMappingJackson2HttpMessageConverter(ConversionService cs, Validator validator) { super(); this.conversionService = cs; this.validator = validator; } /** * {@inheritDoc} */ @Override protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(clazz, null); return readJavaType(javaType, inputMessage); } /** * {@inheritDoc} */ public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { JavaType javaType = getJavaType(type, contextClass); return readJavaType(javaType, inputMessage); } /** * Before call to {@link ObjectMapper#readValue(java.io.InputStream, Class)} * creates a {@link ServletRequestDataBinder} and put it to current Thread * in order to be used by the {@link DataBinderDeserializer}. * <p/> * Ref: <a href= * "http://java.dzone.com/articles/java-thread-local-%E2%80%93-how-use">When * to use Thread Local?</a> * * @param javaType * @param inputMessage * @return */ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { try { Object target = null; String objectName = null; // CRear el DataBinder con un target object en funcion del javaType, // ponerlo en el thread local Class<?> clazz = javaType.getRawClass(); if (Collection.class.isAssignableFrom(clazz)) { Class<?> contentClazz = javaType.getContentType().getRawClass(); target = new DataBinderList<Object>(contentClazz); objectName = "list"; } else if (Map.class.isAssignableFrom(clazz)) { // TODO Class<?> contentClazz = // javaType.getContentType().getRawClass(); target = CollectionFactory.createMap(clazz, 0); objectName = "map"; } else { target = BeanUtils.instantiateClass(clazz); objectName = "bean"; } WebDataBinder binder = new ServletRequestDataBinder(target, objectName); binder.setConversionService(this.conversionService); binder.setAutoGrowNestedPaths(true); binder.setValidator(validator); ThreadLocalUtil.setThreadVariable( BindingResult.MODEL_KEY_PREFIX.concat("JSON_DataBinder"), binder); Object value = getObjectMapper().readValue(inputMessage.getBody(), javaType); return value; } catch (IOException ex) { throw new HttpMessageNotReadableException( "Could not read JSON: ".concat(ex.getMessage()), ex); } } /** * Bean that enable us to bind and validate a list of objects. */ class DataBinderList<T> implements Collection<T> { @Valid protected List<T> list = null; protected Class<?> contentClass = null; public DataBinderList() { this.list = new ArrayList<T>(); } public DataBinderList(Class<?> contentClazz) { this.list = new ArrayList<T>(); this.contentClass = contentClazz; } // Bean interface needed for success data binding --- public List<T> getList() { return list; } public void setList(List<T> list) { this.list = list; } public Class<?> getContentClass() { return contentClass; } public void setContentClass(Class<?> contentClass) { this.contentClass = contentClass; } // Collection interface --- @Override public boolean add(T obj) { return list.add(obj); } @Override public boolean addAll(Collection<? extends T> objects) { return list.addAll(objects); } @Override public void clear() { list.clear(); } @Override public boolean contains(Object obj) { return list.contains(obj); } @Override public boolean containsAll(Collection<?> objects) { return list.containsAll(objects); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public Iterator<T> iterator() { return list.iterator(); } @Override public boolean remove(Object obj) { return list.remove(obj); } @Override public boolean removeAll(Collection<?> objects) { return list.removeAll(objects); } @Override public boolean retainAll(Collection<?> objects) { return list.retainAll(objects); } @Override public int size() { return list.size(); } @Override public Object[] toArray() { return list.toArray(); } @SuppressWarnings("hiding") @Override public <T> T[] toArray(T[] objects) { return list.toArray(objects); } } }