net.solarnetwork.web.support.SimpleCsvHttpMessageConverter.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.web.support.SimpleCsvHttpMessageConverter.java

Source

/* ==================================================================
 * SimpleCsvHttpMessageConverter.java - Dec 3, 2013 2:50:27 PM
 * 
 * Copyright 2007-2013 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
 * ==================================================================
 */

package net.solarnetwork.web.support;

import java.beans.PropertyEditor;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
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 net.solarnetwork.util.ClassUtils;
import net.solarnetwork.util.PropertySerializerRegistrar;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.supercsv.io.CsvMapWriter;
import org.supercsv.io.ICsvMapWriter;
import org.supercsv.prefs.CsvPreference;

/**
 * {@link HttpMessageConverter} that marshals objects into CSV documents.
 * 
 * @author matt
 * @version 1.1
 */
public class SimpleCsvHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    /**
     * The default value for the <code>javaBeanIgnoreProperties</code> property.
     */
    public static final String[] DEFAULT_JAVA_BEAN_IGNORE_PROPERTIES = new String[] { "class", };

    /**
     * The default value for the <code>javaBeanTreatAsStringValues</code>
     * property.
     */
    public static final Class<?>[] DEFAULT_JAVA_BEAN_STRING_VALUES = new Class<?>[] { Class.class, };

    private PropertySerializerRegistrar propertySerializerRegistrar = null;
    private Set<String> javaBeanIgnoreProperties = new LinkedHashSet<String>(
            Arrays.asList(DEFAULT_JAVA_BEAN_IGNORE_PROPERTIES));
    private Set<Class<?>> javaBeanTreatAsStringValues = new LinkedHashSet<Class<?>>(
            Arrays.asList(DEFAULT_JAVA_BEAN_STRING_VALUES));
    private boolean includeHeader = true;

    /**
     * Default constructor.
     */
    public SimpleCsvHttpMessageConverter() {
        super(new MediaType("text", "csv", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        throw new UnsupportedOperationException("Reading CSV is not supported.");
    }

    @Override
    protected void writeInternal(Object t, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        Iterable<?> rows = null;
        if (t instanceof Iterable) {
            rows = (Iterable<?>) t;
        } else {
            // see if object has an Iterable property on it
            Map<String, Object> props = ClassUtils.getBeanProperties(t, javaBeanIgnoreProperties);
            for (Object o : props.values()) {
                if (o instanceof Iterable) {
                    rows = (Iterable<?>) o;
                    break;
                }
            }
            if (rows == null) {
                List<Object> tmpList = new ArrayList<Object>(1);
                tmpList.add(t);
                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, null);
        final String[] fields = fieldList.toArray(new String[fieldList.size()]);

        if (fields == null || fields.length < 1) {
            // could happen with empty Map, for example
            return;
        }

        final ICsvMapWriter writer = new CsvMapWriter(new OutputStreamWriter(outputMessage.getBody(), "UTF-8"),
                CsvPreference.EXCEL_PREFERENCE);
        try {
            // output header
            if (includeHeader) {
                writer.writeHeader(fields);
            }

            // output first row
            writeCSV(writer, fields, row);

            // output remainder rows
            while (rowIterator.hasNext()) {
                row = rowIterator.next();
                writeCSV(writer, fields, row);
            }
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    // ignore these
                }
            }
        }
    }

    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 (propertySerializerRegistrar != null) {
                // try whole-bean serialization first
                Object o = propertySerializerRegistrar.serializeProperty("row", row.getClass(), row, row);
                if (o != row) {
                    if (o != null) {
                        result = getCSVFields(o, fieldOrder);
                        return result;
                    }
                }
            }
            Map<String, Object> props = ClassUtils.getBeanProperties(row, javaBeanIgnoreProperties, true);
            result = getCSVFields(props, fieldOrder);
        }
        return result;
    }

    // this method exists so we don't have to add @SuppressWarnings to other (real) methods
    @SuppressWarnings("unchecked")
    private <T> T cast(Object o) {
        return (T) o;
    }

    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 (propertySerializerRegistrar != null) {
                // try whole-bean serialization first
                row = propertySerializerRegistrar.serializeProperty("row", row.getClass(), row, row);
                if (row == null) {
                    return;
                }
            }

            if (row instanceof Map) {
                Map<String, ?> rowMap = cast(row);
                for (Map.Entry<String, ?> me : rowMap.entrySet()) {
                    Object val = getRowPropertyValue(row, me.getKey(), me.getValue(), null);
                    if (val != null) {
                        map.put(me.getKey(), val);
                    }
                }
            } else {
                BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(row);
                for (String name : fields) {
                    Object val = wrapper.getPropertyValue(name);
                    val = getRowPropertyValue(row, name, val, wrapper);
                    if (val != null) {
                        map.put(name, val);
                    }
                }
            }

            writer.write(map, fields);
        }
    }

    private Object getRowPropertyValue(Object row, String name, Object val, BeanWrapper wrapper) {
        if (val != null) {
            if (getPropertySerializerRegistrar() != null) {
                val = getPropertySerializerRegistrar().serializeProperty(name, val.getClass(), row, val);
            } else if (wrapper != null) {
                // 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<?> || javaBeanTreatAsStringValues != null
                    && javaBeanTreatAsStringValues.contains(val.getClass())) {
                val = val.toString();
            }
        }
        return val;
    }

    public PropertySerializerRegistrar getPropertySerializerRegistrar() {
        return propertySerializerRegistrar;
    }

    public void setPropertySerializerRegistrar(PropertySerializerRegistrar propertySerializerRegistrar) {
        this.propertySerializerRegistrar = propertySerializerRegistrar;
    }

    public Set<String> getJavaBeanIgnoreProperties() {
        return javaBeanIgnoreProperties;
    }

    public void setJavaBeanIgnoreProperties(Set<String> javaBeanIgnoreProperties) {
        this.javaBeanIgnoreProperties = javaBeanIgnoreProperties;
    }

    public Set<Class<?>> getJavaBeanTreatAsStringValues() {
        return javaBeanTreatAsStringValues;
    }

    public void setJavaBeanTreatAsStringValues(Set<Class<?>> javaBeanTreatAsStringValues) {
        this.javaBeanTreatAsStringValues = javaBeanTreatAsStringValues;
    }

    public boolean isIncludeHeader() {
        return includeHeader;
    }

    public void setIncludeHeader(boolean includeHeader) {
        this.includeHeader = includeHeader;
    }

}