org.apache.solr.response.CSVResponseWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.response.CSVResponseWriter.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.response;

import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVStrategy;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Fieldable;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.DateUtil;
import org.apache.solr.common.util.FastWriter;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.uninverted.UnInvertedField;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.StrField;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocList;
import org.apache.solr.search.SolrIndexSearcher;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;

/**
 * @version $Id: CSVResponseWriter.java 979838 2010-07-27 20:13:15Z yonik $
 */

public class CSVResponseWriter implements QueryResponseWriter {

    public void init(NamedList n) {
    }

    public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
        CSVWriter w = new CSVWriter(writer, req, rsp);
        try {
            w.writeResponse();
        } finally {
            w.close();
        }
    }

    public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
        // using the text/plain allows this to be viewed in the browser easily
        return CONTENT_TYPE_TEXT_UTF8;
    }
}

class CSVWriter extends TextResponseWriter {
    static String SEPARATOR = "separator";
    static String ENCAPSULATOR = "encapsulator";
    static String ESCAPE = "escape";

    static String CSV = "csv.";
    static String CSV_SEPARATOR = CSV + SEPARATOR;
    static String CSV_ENCAPSULATOR = CSV + ENCAPSULATOR;
    static String CSV_ESCAPE = CSV + ESCAPE;

    static String MV = CSV + "mv.";
    static String MV_SEPARATOR = MV + SEPARATOR;
    static String MV_ENCAPSULATOR = MV + ENCAPSULATOR;
    static String MV_ESCAPE = MV + ESCAPE;

    static String CSV_NULL = CSV + "null";
    static String CSV_HEADER = CSV + "header";
    static String CSV_NEWLINE = CSV + "newline";

    char[] sharedCSVBuf = new char[8192];

    // prevent each instance from creating it's own buffer
    class CSVSharedBufPrinter extends CSVPrinter {
        public CSVSharedBufPrinter(Writer out, CSVStrategy strategy) {
            super(out, strategy);
            super.buf = sharedCSVBuf;
        }

        public void reset() {
            super.newLine = true;
            // update our shared buf in case a new bigger one was allocated
            sharedCSVBuf = super.buf;
        }
    }

    // allows access to internal buf w/o copying it
    static class OpenCharArrayWriter extends CharArrayWriter {
        public char[] getInternalBuf() {
            return buf;
        }
    }

    // Writes all data to a char array,
    // allows access to internal buffer, and allows fast resetting.
    static class ResettableFastWriter extends FastWriter {
        OpenCharArrayWriter cw = new OpenCharArrayWriter();
        char[] result;
        int resultLen;

        public ResettableFastWriter() {
            super(new OpenCharArrayWriter());
            cw = (OpenCharArrayWriter) sink;
        }

        public void reset() {
            cw.reset();
            pos = 0;
        }

        public void freeze() throws IOException {
            if (cw.size() > 0) {
                flush();
                result = cw.getInternalBuf();
                resultLen = cw.size();
            } else {
                result = buf;
                resultLen = pos;
            }
        }

        public int getFrozenSize() {
            return resultLen;
        }

        public char[] getFrozenBuf() {
            return result;
        }
    }

    static class CSVField {
        String name;
        SchemaField sf;
        CSVSharedBufPrinter mvPrinter; // printer used to encode multiple values in a single CSV value

        // used to collect values
        List<Fieldable> values = new ArrayList<Fieldable>(1); // low starting amount in case there are many fields
        int tmp;
    }

    int pass;
    Map<String, CSVField> csvFields = new LinkedHashMap<String, CSVField>();

    Calendar cal; // for formatting date objects

    CSVStrategy strategy; // strategy for encoding the fields of documents
    CSVPrinter printer;
    ResettableFastWriter mvWriter = new ResettableFastWriter(); // writer used for multi-valued fields

    String NullValue;
    boolean returnScore = false;

    public CSVWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
        super(writer, req, rsp);
    }

    public void writeResponse() throws IOException {
        SolrParams params = req.getParams();

        strategy = new CSVStrategy(',', '"', CSVStrategy.COMMENTS_DISABLED, CSVStrategy.ESCAPE_DISABLED, false,
                false, false, true);
        CSVStrategy strat = strategy;

        String sep = params.get(CSV_SEPARATOR);
        if (sep != null) {
            if (sep.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid separator:'" + sep + "'");
            strat.setDelimiter(sep.charAt(0));
        }

        String nl = params.get(CSV_NEWLINE);
        if (nl != null) {
            if (nl.length() == 0)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid newline:'" + nl + "'");
            strat.setPrinterNewline(nl);
        }

        String encapsulator = params.get(CSV_ENCAPSULATOR);
        String escape = params.get(CSV_ESCAPE);
        if (encapsulator != null) {
            if (encapsulator.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                        "Invalid encapsulator:'" + encapsulator + "'");
            strat.setEncapsulator(encapsulator.charAt(0));
        }

        if (escape != null) {
            if (escape.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid escape:'" + escape + "'");
            strat.setEscape(escape.charAt(0));
            if (encapsulator == null) {
                strat.setEncapsulator(CSVStrategy.ENCAPSULATOR_DISABLED);
            }
        }

        if (strat.getEscape() == '\\') {
            // If the escape is the standard backslash, then also enable
            // unicode escapes (it's harmless since 'u' would not otherwise
            // be escaped.
            strat.setUnicodeEscapeInterpretation(true);
        }

        printer = new CSVPrinter(writer, strategy);

        CSVStrategy mvStrategy = new CSVStrategy(strategy.getDelimiter(), CSVStrategy.ENCAPSULATOR_DISABLED,
                CSVStrategy.COMMENTS_DISABLED, '\\', false, false, false, false);
        strat = mvStrategy;

        sep = params.get(MV_SEPARATOR);
        if (sep != null) {
            if (sep.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid mv separator:'" + sep + "'");
            strat.setDelimiter(sep.charAt(0));
        }

        encapsulator = params.get(MV_ENCAPSULATOR);
        escape = params.get(MV_ESCAPE);

        if (encapsulator != null) {
            if (encapsulator.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                        "Invalid mv encapsulator:'" + encapsulator + "'");
            strat.setEncapsulator(encapsulator.charAt(0));
            if (escape == null) {
                strat.setEscape(CSVStrategy.ESCAPE_DISABLED);
            }
        }

        escape = params.get(MV_ESCAPE);
        if (escape != null) {
            if (escape.length() != 1)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid mv escape:'" + escape + "'");
            strat.setEscape(escape.charAt(0));
            // encapsulator will already be disabled if it wasn't specified
        }

        returnScore = returnFields != null && returnFields.contains("score");
        boolean needListOfFields = returnFields == null || returnFields.size() == 0
                || (returnFields.size() == 1 && returnScore) || returnFields.contains("*");
        Collection<String> fields = returnFields;

        Object responseObj = rsp.getValues().get("response");
        if (needListOfFields) {
            if (responseObj instanceof SolrDocumentList) {
                // get the list of fields from the SolrDocumentList
                fields = new LinkedHashSet<String>();
                for (SolrDocument sdoc : (SolrDocumentList) responseObj) {
                    fields.addAll(sdoc.getFieldNames());
                }
            } else {
                // get the list of fields from the index
                fields = req.getSearcher().getFieldNames();
            }
            if (returnScore) {
                fields.add("score");
            } else {
                fields.remove("score");
            }
        }

        CSVSharedBufPrinter csvPrinterMV = new CSVSharedBufPrinter(mvWriter, mvStrategy);

        for (String field : fields) {
            if (field.equals("score")) {
                CSVField csvField = new CSVField();
                csvField.name = "score";
                csvFields.put("score", csvField);
                continue;
            }

            SchemaField sf = schema.getFieldOrNull(field);
            if (sf == null) {
                FieldType ft = new StrField();
                sf = new SchemaField(field, ft);
            }

            // if we got the list of fields from the index, only list stored fields
            if (returnFields == null && sf != null && !sf.stored()) {
                continue;
            }

            // check for per-field overrides
            sep = params.get("f." + field + '.' + CSV_SEPARATOR);
            encapsulator = params.get("f." + field + '.' + CSV_ENCAPSULATOR);
            escape = params.get("f." + field + '.' + CSV_ESCAPE);

            CSVSharedBufPrinter csvPrinter = csvPrinterMV;
            if (sep != null || encapsulator != null || escape != null) {
                // create a new strategy + printer if there were any per-field overrides
                strat = (CSVStrategy) mvStrategy.clone();
                if (sep != null) {
                    if (sep.length() != 1)
                        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                                "Invalid mv separator:'" + sep + "'");
                    strat.setDelimiter(sep.charAt(0));
                }
                if (encapsulator != null) {
                    if (encapsulator.length() != 1)
                        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                                "Invalid mv encapsulator:'" + encapsulator + "'");
                    strat.setEncapsulator(encapsulator.charAt(0));
                    if (escape == null) {
                        strat.setEscape(CSVStrategy.ESCAPE_DISABLED);
                    }
                }
                if (escape != null) {
                    if (escape.length() != 1)
                        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                                "Invalid mv escape:'" + escape + "'");
                    strat.setEscape(escape.charAt(0));
                    if (encapsulator == null) {
                        strat.setEncapsulator(CSVStrategy.ENCAPSULATOR_DISABLED);
                    }
                }
                csvPrinter = new CSVSharedBufPrinter(mvWriter, strat);
            }

            CSVField csvField = new CSVField();
            csvField.name = field;
            csvField.sf = sf;
            csvField.mvPrinter = csvPrinter;
            csvFields.put(field, csvField);
        }

        NullValue = params.get(CSV_NULL, "");

        if (params.getBool(CSV_HEADER, true)) {
            for (CSVField csvField : csvFields.values()) {
                printer.print(csvField.name);
            }
            printer.println();
        }

        if (responseObj instanceof DocList) {
            writeDocList(null, (DocList) responseObj, null, null);
        } else if (responseObj instanceof SolrDocumentList) {
            writeSolrDocumentList(null, (SolrDocumentList) responseObj, null, null);
        }

    }

    @Override
    public void close() throws IOException {
        if (printer != null)
            printer.flush();
        super.close();
    }

    @Override
    public void writeNamedList(String name, NamedList val) throws IOException {
    }

    @Override
    public void writeDoc(String name, Document doc, Set<String> returnFields, float score, boolean includeScore)
            throws IOException {
        pass++;

        for (Fieldable field : doc.getFields()) {
            CSVField csvField = csvFields.get(field.name());
            if (csvField == null)
                continue;
            if (csvField.tmp != pass) {
                csvField.tmp = pass;
                csvField.values.clear();
            }
            csvField.values.add(field);
        }

        for (CSVField csvField : csvFields.values()) {
            if (csvField.name.equals("score")) {
                writeFloat("score", score);
                continue;
            }
            if (csvField.tmp != pass) {
                writeNull(csvField.name);
                continue;
            }

            if (csvField.sf.multiValued() || csvField.values.size() > 1) {
                mvWriter.reset();
                csvField.mvPrinter.reset();
                // switch the printer to use the multi-valued one
                CSVPrinter tmp = printer;
                printer = csvField.mvPrinter;
                for (Fieldable fval : csvField.values) {
                    csvField.sf.getType().write(this, csvField.name, fval);
                }
                printer = tmp; // restore the original printer

                mvWriter.freeze();
                printer.print(mvWriter.getFrozenBuf(), 0, mvWriter.getFrozenSize(), true);
            } else {
                assert csvField.values.size() == 1;
                csvField.sf.getType().write(this, csvField.name, csvField.values.get(0));
            }
        }

        printer.println();
    }

    //NOTE: a document cannot currently contain another document
    List tmpList;

    @Override
    public void writeSolrDocument(String name, SolrDocument doc, Set<String> returnFields, Map pseudoFields)
            throws IOException {
        if (tmpList == null) {
            tmpList = new ArrayList(1);
            tmpList.add(null);
        }

        for (CSVField csvField : csvFields.values()) {
            Object val = doc.getFieldValue(csvField.name);
            int nVals = val instanceof Collection ? ((Collection) val).size() : (val == null ? 0 : 1);
            if (nVals == 0) {
                writeNull(csvField.name);
                continue;
            }

            if ((csvField.sf != null && csvField.sf.multiValued()) || nVals > 1) {
                Collection values;
                // normalize to a collection
                if (val instanceof Collection) {
                    values = (Collection) val;
                } else {
                    tmpList.set(0, val);
                    values = tmpList;
                }

                mvWriter.reset();
                csvField.mvPrinter.reset();
                // switch the printer to use the multi-valued one
                CSVPrinter tmp = printer;
                printer = csvField.mvPrinter;
                for (Object fval : values) {
                    writeVal(csvField.name, fval);
                }
                printer = tmp; // restore the original printer

                mvWriter.freeze();
                printer.print(mvWriter.getFrozenBuf(), 0, mvWriter.getFrozenSize(), true);

            } else {
                // normalize to first value
                if (val instanceof Collection) {
                    Collection values = (Collection) val;
                    val = values.iterator().next();
                }
                writeVal(csvField.name, val);
            }
        }

        printer.println();
    }

    @Override
    public void writeDocList(String name, DocList ids, Set<String> fields, Map otherFields) throws IOException {
        int sz = ids.size();
        SolrIndexSearcher searcher = req.getSearcher();
        DocIterator iterator = ids.iterator();
        for (int i = 0; i < sz; i++) {
            int id = iterator.nextDoc();
            Document doc = searcher.doc(id, fields);
            writeDoc(null, doc, fields, (returnScore ? iterator.score() : 0.0f), returnScore);
        }
    }

    Map scoreMap = new HashMap(1);

    @Override
    public void writeSolrDocumentList(String name, SolrDocumentList docs, Set<String> fields, Map otherFields)
            throws IOException {
        for (SolrDocument doc : docs) {
            writeSolrDocument(name, doc, fields, otherFields);
        }
    }

    @Override
    public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
        printer.print(val, needsEscaping);
    }

    @Override
    public void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException {
    }

    @Override
    public void writeArray(String name, Object[] val) throws IOException {
    }

    @Override
    public void writeArray(String name, Iterator val) throws IOException {
    }

    @Override
    public void writeNull(String name) throws IOException {
        printer.print(NullValue);
    }

    @Override
    public void writeInt(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeLong(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeBool(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeFloat(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeDouble(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeDate(String name, Date val) throws IOException {
        StringBuilder sb = new StringBuilder(25);
        cal = DateUtil.formatDate(val, cal, sb);
        writeDate(name, sb.toString());
    }

    @Override
    public void writeDate(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeShort(String name, String val) throws IOException {
        printer.print(val, false);
    }

    @Override
    public void writeByte(String name, String val) throws IOException {
        printer.print(val, false);
    }
}