/* ================================================================== * SimpleCsvView.java - Feb 11, 2012 3:03:32 PM * * Copyright 2007-2012 SolarNetwork.net Dev Team * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== * $Id$ * ================================================================== */ package net.solarnetwork.web.support; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.solarnetwork.util.SerializeIgnore; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; import org.supercsv.io.CsvMapWriter; import org.supercsv.io.ICsvMapWriter; import org.supercsv.prefs.CsvPreference; /** * Spring {@link org.springframework.web.servlet.View} for turning objects into * CSV through JavaBean introspection. * * <p> * The character encoding of the output must be specified in the * {@link #setContentType(String)} (e.g. {@literal text/csv;charset=UTF-8}). * </p> * * <p> * The configurable properties of this class are: * </p> * * <dl> * <dt>dataModelKey</dt> * <dd>If not <em>null</em>, then use this model key as the data object to * render as CSV. Otherwise, export just the first available key's associated * object. Defaults to {@link #DEFAULT_DATA_MODEL_KEY}.</dd> * * <dt>fieldOrderKey</dt> * <dd>If not <em>null</em>, then use this model key as an ordered Collection of * exported field names, such that the CSV columns will be exported in the * specified order. If not specified, then for Map objects the output order will * be determined by the natural iteration order of the Map keys, and for * JavaBean objects the bean properties will be exported in case-insensitive * alphabetical order. Defaults to {@link #DEFAULT_FIELD_ORDER_KEY}.</dd> * </dl> * * * @author matt * @version $Revision$ */ public class SimpleCsvView extends AbstractView { /** Default content type. */ public static final String DEFAULT_CSV_CONTENT_TYPE = "text/csv;charset=UTF-8"; /** The default value for the {@code dataModelKey} property. */ public static final String DEFAULT_DATA_MODEL_KEY = "data"; /** The default value for the {@code fieldOrderKey} property. */ public static final String DEFAULT_FIELD_ORDER_KEY = "fieldOrder"; private String dataModelKey = DEFAULT_DATA_MODEL_KEY; private String fieldOrderKey = DEFAULT_FIELD_ORDER_KEY; /** * Default constructor. */ public SimpleCsvView() { super(); setContentType(DEFAULT_CSV_CONTENT_TYPE); } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if ( model.isEmpty() ) { return; } final String charset = getResponseCharacterEncoding(); response.setCharacterEncoding(charset); response.setContentType(getContentType()); final Object data = (dataModelKey != null && model.containsKey(dataModelKey) ? model .get(dataModelKey) : model); if ( data == null ) { return; } @SuppressWarnings("unchecked") final Collection<String> fieldOrder = (fieldOrderKey != null && model.get(fieldOrderKey) instanceof Collection ? (Collection<String>) model .get(fieldOrderKey) : null); Iterable<?> rows = null; if ( data instanceof Iterable ) { rows = (Iterable<?>) data; } else { List<Object> tmpList = new ArrayList<Object>(1); tmpList.add(data); rows = tmpList; } Object row = null; Iterator<?> rowIterator = rows.iterator(); if ( !rowIterator.hasNext() ) { return; } // get first row, to use for fields row = rowIterator.next(); if ( row == null ) { return; } final List<String> fieldList = getCSVFields(row, fieldOrder); final String[] fields = fieldList.toArray(new String[fieldList.size()]); final ICsvMapWriter writer = new CsvMapWriter(response.getWriter(), CsvPreference.EXCEL_PREFERENCE); try { // output header if ( true ) { // TODO make configurable property Map<String, String> headerMap = new HashMap<String, String>(fields.length); for ( String field : fields ) { headerMap.put(field, field); } writeCSV(writer, fields, headerMap); } // output first row writeCSV(writer, fields, row); // output remainder rows while ( rowIterator.hasNext() ) { row = rowIterator.next(); writeCSV(writer, fields, row); } } finally { writer.flush(); writer.close(); } } private List<String> getCSVFields(Object row, final Collection<String> fieldOrder) { assert row != null; List<String> result = new ArrayList<String>(); if ( row instanceof Map ) { Map<?, ?> map = (Map<?, ?>) row; if ( fieldOrder != null ) { for ( String key : fieldOrder ) { result.add(key); } } else { for ( Object key : map.keySet() ) { result.add(key.toString()); } } } else { // use bean properties if ( getPropertySerializerRegistrar() != null ) { // try whole-bean serialization first Object o = getPropertySerializerRegistrar().serializeProperty("row", row.getClass(), row, row); if ( o != row ) { if ( o != null ) { result = getCSVFields(o, fieldOrder); return result; } } } BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(row); PropertyDescriptor[] props = wrapper.getPropertyDescriptors(); Set<String> resultSet = new LinkedHashSet<String>(); for ( PropertyDescriptor prop : props ) { String name = prop.getName(); if ( getJavaBeanIgnoreProperties() != null && getJavaBeanIgnoreProperties().contains(name) ) { continue; } if ( wrapper.isReadableProperty(name) ) { // test for SerializeIgnore Method getter = prop.getReadMethod(); if ( getter != null && getter.isAnnotationPresent(SerializeIgnore.class) ) { continue; } resultSet.add(name); } } if ( fieldOrder != null && fieldOrder.size() > 0 ) { for ( String key : fieldOrder ) { if ( resultSet.contains(key) ) { result.add(key); } } } else { result.addAll(resultSet); } } return result; } private void writeCSV(ICsvMapWriter writer, String[] fields, Object row) throws IOException { if ( row instanceof Map ) { @SuppressWarnings("unchecked") Map<String, ?> map = (Map<String, ?>) row; writer.write(map, fields); } else if ( row != null ) { Map<String, Object> map = new HashMap<String, Object>(fields.length); // use bean properties if ( getPropertySerializerRegistrar() != null ) { // try whole-bean serialization first row = getPropertySerializerRegistrar() .serializeProperty("row", row.getClass(), row, row); if ( row == null ) { return; } } BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(row); for ( String name : fields ) { Object val = wrapper.getPropertyValue(name); if ( val != null ) { if ( getPropertySerializerRegistrar() != null ) { val = getPropertySerializerRegistrar().serializeProperty(name, val.getClass(), row, val); } else { // Spring does not apply PropertyEditors on read methods, so manually handle PropertyEditor editor = wrapper.findCustomEditor(null, name); if ( editor != null ) { editor.setValue(val); val = editor.getAsText(); } } if ( val instanceof Enum<?> || getJavaBeanTreatAsStringValues() != null && getJavaBeanTreatAsStringValues().contains(val.getClass()) ) { val = val.toString(); } if ( val != null ) { map.put(name, val); } } } writer.write(map, fields); } } public String getDataModelKey() { return dataModelKey; } public void setDataModelKey(String dataModelKey) { this.dataModelKey = dataModelKey; } public String getFieldOrderKey() { return fieldOrderKey; } public void setFieldOrderKey(String fieldOrderKey) { this.fieldOrderKey = fieldOrderKey; } }