blob: 2ba8495ad5648e8130402f0f64874d933b508ad0 [file] [log] [blame]
/*
* 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 java.io.IOException;
import java.io.Writer;
import java.util.*;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.Base64;
import org.apache.solr.util.FastWriter;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.transform.DocTransformer;
import org.apache.solr.response.transform.TransformContext;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.DateField;
import org.apache.solr.search.DocList;
import org.apache.solr.search.ReturnFields;
/** Base class for text-oriented response writers.
*
*
*/
public abstract class TextResponseWriter {
// indent up to 40 spaces
static final char[] indentChars = new char[81];
static {
Arrays.fill(indentChars,' ');
indentChars[0] = '\n'; // start with a newline
}
protected final FastWriter writer;
protected final IndexSchema schema;
protected final SolrQueryRequest req;
protected final SolrQueryResponse rsp;
// the default set of fields to return for each document
protected ReturnFields returnFields;
protected int level;
protected boolean doIndent;
protected Calendar cal; // reusable calendar instance
public TextResponseWriter(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) {
this.writer = FastWriter.wrap(writer);
this.schema = req.getSchema();
this.req = req;
this.rsp = rsp;
String indent = req.getParams().get("indent");
if (indent != null && !"".equals(indent) && !"off".equals(indent)) {
doIndent=true;
}
returnFields = rsp.getReturnFields();
}
/** done with this ResponseWriter... make sure any buffers are flushed to writer */
public void close() throws IOException {
writer.flushBuffer();
}
/** returns the Writer that the response is being written to */
public Writer getWriter() { return writer; }
public void indent() throws IOException {
if (doIndent) indent(level);
}
public void indent(int lev) throws IOException {
writer.write(indentChars, 0, Math.min((lev<<1)+1, indentChars.length));
}
//
// Functions to manipulate the current logical nesting level.
// Any indentation will be partially based on level.
//
public void setLevel(int level) { this.level = level; }
public int level() { return level; }
public int incLevel() { return ++level; }
public int decLevel() { return --level; }
public void setIndent(boolean doIndent) {
this.doIndent = doIndent;
}
public abstract void writeNamedList(String name, NamedList val) throws IOException;
public final void writeVal(String name, Object val) throws IOException {
// if there get to be enough types, perhaps hashing on the type
// to get a handler might be faster (but types must be exact to do that...)
// go in order of most common to least common
if (val==null) {
writeNull(name);
} else if (val instanceof String) {
writeStr(name, val.toString(), true);
// micro-optimization... using toString() avoids a cast first
} else if (val instanceof IndexableField) {
IndexableField f = (IndexableField)val;
SchemaField sf = schema.getFieldOrNull( f.name() );
if( sf != null ) {
sf.getType().write(this, name, f);
}
else {
writeStr(name, f.stringValue(), true);
}
} else if (val instanceof Number) {
if (val instanceof Integer) {
writeInt(name, val.toString());
} else if (val instanceof Long) {
writeLong(name, val.toString());
} else if (val instanceof Float) {
// we pass the float instead of using toString() because
// it may need special formatting. same for double.
writeFloat(name, ((Float)val).floatValue());
} else if (val instanceof Double) {
writeDouble(name, ((Double)val).doubleValue());
} else if (val instanceof Short) {
writeInt(name, val.toString());
} else if (val instanceof Byte) {
writeInt(name, val.toString());
} else {
// default... for debugging only
writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
}
} else if (val instanceof Boolean) {
writeBool(name, val.toString());
} else if (val instanceof Date) {
writeDate(name,(Date)val);
} else if (val instanceof Document) {
SolrDocument doc = toSolrDocument( (Document)val );
DocTransformer transformer = returnFields.getTransformer();
if( transformer != null ) {
TransformContext context = new TransformContext();
context.req = req;
transformer.setContext(context);
transformer.transform(doc, -1);
}
writeSolrDocument(name, doc, returnFields, 0 );
} else if (val instanceof SolrDocument) {
writeSolrDocument(name, (SolrDocument)val, returnFields, 0);
} else if (val instanceof ResultContext) {
// requires access to IndexReader
writeDocuments(name, (ResultContext)val, returnFields);
} else if (val instanceof DocList) {
// Should not happen normally
ResultContext ctx = new ResultContext();
ctx.docs = (DocList)val;
writeDocuments(name, ctx, returnFields);
// }
// else if (val instanceof DocSet) {
// how do we know what fields to read?
// todo: have a DocList/DocSet wrapper that
// restricts the fields to write...?
} else if (val instanceof SolrDocumentList) {
writeSolrDocumentList(name, (SolrDocumentList)val, returnFields);
} else if (val instanceof Map) {
writeMap(name, (Map)val, false, true);
} else if (val instanceof NamedList) {
writeNamedList(name, (NamedList)val);
} else if (val instanceof Iterable) {
writeArray(name,((Iterable)val).iterator());
} else if (val instanceof Object[]) {
writeArray(name,(Object[])val);
} else if (val instanceof Iterator) {
writeArray(name,(Iterator)val);
} else if (val instanceof byte[]) {
byte[] arr = (byte[])val;
writeByteArr(name, arr, 0, arr.length);
} else if (val instanceof BytesRef) {
BytesRef arr = (BytesRef)val;
writeByteArr(name, arr.bytes, arr.offset, arr.length);
} else {
// default... for debugging only
writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
}
}
// names are passed when writing primitives like writeInt to allow many different
// types of formats, including those where the name may come after the value (like
// some XML formats).
public abstract void writeStartDocumentList(String name, long start, int size, long numFound, Float maxScore) throws IOException;
public abstract void writeSolrDocument(String name, SolrDocument doc, ReturnFields returnFields, int idx) throws IOException;
public abstract void writeEndDocumentList() throws IOException;
// Assume each SolrDocument is already transformed
public final void writeSolrDocumentList(String name, SolrDocumentList docs, ReturnFields returnFields) throws IOException
{
writeStartDocumentList(name, docs.getStart(), docs.size(), docs.getNumFound(), docs.getMaxScore() );
for( int i=0; i<docs.size(); i++ ) {
writeSolrDocument( null, docs.get(i), returnFields, i );
}
writeEndDocumentList();
}
public final SolrDocument toSolrDocument( Document doc )
{
return ResponseWriterUtil.toSolrDocument(doc, schema);
}
public final void writeDocuments(String name, ResultContext res, ReturnFields fields ) throws IOException {
DocList ids = res.docs;
TransformContext context = new TransformContext();
context.query = res.query;
context.wantsScores = fields.wantsScore() && ids.hasScores();
context.req = req;
writeStartDocumentList(name, ids.offset(), ids.size(), ids.matches(),
context.wantsScores ? new Float(ids.maxScore()) : null );
DocTransformer transformer = fields.getTransformer();
context.searcher = req.getSearcher();
context.iterator = ids.iterator();
if( transformer != null ) {
transformer.setContext( context );
}
int sz = ids.size();
Set<String> fnames = fields.getLuceneFieldNames();
for (int i=0; i<sz; i++) {
int id = context.iterator.nextDoc();
Document doc = context.searcher.doc(id, fnames);
SolrDocument sdoc = toSolrDocument( doc );
if( transformer != null ) {
transformer.transform( sdoc, id);
}
writeSolrDocument( null, sdoc, returnFields, i );
}
if( transformer != null ) {
transformer.setContext( null );
}
writeEndDocumentList();
}
public abstract void writeStr(String name, String val, boolean needsEscaping) throws IOException;
public abstract void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException;
public void writeArray(String name, Object[] val) throws IOException {
writeArray(name, Arrays.asList(val).iterator());
}
public abstract void writeArray(String name, Iterator val) throws IOException;
public abstract void writeNull(String name) throws IOException;
/** if this form of the method is called, val is the Java string form of an int */
public abstract void writeInt(String name, String val) throws IOException;
public void writeInt(String name, int val) throws IOException {
writeInt(name,Integer.toString(val));
}
/** if this form of the method is called, val is the Java string form of a long */
public abstract void writeLong(String name, String val) throws IOException;
public void writeLong(String name, long val) throws IOException {
writeLong(name,Long.toString(val));
}
/** if this form of the method is called, val is the Java string form of a boolean */
public abstract void writeBool(String name, String val) throws IOException;
public void writeBool(String name, boolean val) throws IOException {
writeBool(name,Boolean.toString(val));
}
/** if this form of the method is called, val is the Java string form of a float */
public abstract void writeFloat(String name, String val) throws IOException;
public void writeFloat(String name, float val) throws IOException {
String s = Float.toString(val);
// If it's not a normal number, write the value as a string instead.
// The following test also handles NaN since comparisons are always false.
if (val > Float.NEGATIVE_INFINITY && val < Float.POSITIVE_INFINITY) {
writeFloat(name,s);
} else {
writeStr(name,s,false);
}
}
/** if this form of the method is called, val is the Java string form of a double */
public abstract void writeDouble(String name, String val) throws IOException;
public void writeDouble(String name, double val) throws IOException {
String s = Double.toString(val);
// If it's not a normal number, write the value as a string instead.
// The following test also handles NaN since comparisons are always false.
if (val > Double.NEGATIVE_INFINITY && val < Double.POSITIVE_INFINITY) {
writeDouble(name,s);
} else {
writeStr(name,s,false);
}
}
public void writeDate(String name, Date val) throws IOException {
writeDate(name, DateField.formatExternal(val));
}
/** if this form of the method is called, val is the Solr ISO8601 based date format */
public abstract void writeDate(String name, String val) throws IOException;
public void writeByteArr(String name, byte[] buf, int offset, int len) throws IOException {
writeStr(name, Base64.byteArrayToBase64(buf, offset, len), false);
}
}