Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-17682: QueryResponseWriter hierarchy refactor #3209

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.BinaryResponseWriter;
import org.apache.solr.response.QueryResponseWriterUtil;
import org.apache.solr.response.ResultContext;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.servlet.SolrRequestParsers;
Expand Down Expand Up @@ -276,8 +275,7 @@ ByteArrayInputStream toInputStream() {
};

if (callback == null) {
QueryResponseWriterUtil.writeQueryResponse(
byteBuffer, req.getResponseWriter(), req, rsp, null);
req.getResponseWriter().write(byteBuffer, req, rsp);
} else {
// mostly stream results to the callback; rest goes into the byteBuffer
if (!(responseParser instanceof BinaryResponseParser))
Expand Down
3 changes: 2 additions & 1 deletion solr/core/src/java/org/apache/solr/core/SolrCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -3053,7 +3053,8 @@ public PluginBag<QueryResponseWriter> getResponseWriters() {
private static BinaryResponseWriter getFileStreamWriter() {
return new BinaryResponseWriter() {
@Override
public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response)
public void write(
OutputStream out, SolrQueryRequest req, SolrQueryResponse response, String contentType)
throws IOException {
RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationAPIBase.FILE_STREAM);
if (rawWriter != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import org.apache.solr.response.BinaryResponseWriter;
import org.apache.solr.response.CSVResponseWriter;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.response.QueryResponseWriterUtil;
import org.apache.solr.response.RawResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.response.XMLResponseWriter;
Expand Down Expand Up @@ -141,8 +140,7 @@ public void writeTo(
(SolrQueryResponse) requestContext.getProperty(SOLR_QUERY_RESPONSE);

V2ApiUtils.squashIntoSolrResponseWithHeader(solrQueryResponse, toWrite);
QueryResponseWriterUtil.writeQueryResponse(
entityStream, responseWriter, solrQueryRequest, solrQueryResponse, mediaType.toString());
responseWriter.write(entityStream, solrQueryRequest, solrQueryResponse, mediaType.toString());
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -45,12 +44,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BinaryResponseWriter implements BinaryQueryResponseWriter {
/** Solr's "javabin" format. TODO rename accordingly. */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth doing in another PR; maybe same JIRA

public class BinaryResponseWriter implements QueryResponseWriter {
// public static boolean useUtf8CharSeq = true;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@Override
public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response)
public void write(
OutputStream out, SolrQueryRequest req, SolrQueryResponse response, String contentType)
throws IOException {
Resolver resolver = new Resolver(req, response.getReturnFields());
if (req.getParams().getBool(CommonParams.OMIT_HEADER, false)) response.removeResponseHeader();
Expand All @@ -59,12 +60,6 @@ public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse resp
}
}

@Override
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response)
throws IOException {
throw new RuntimeException("This is a binary writer , Cannot write to a characterstream");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existence of this was a code-smell that the hierarchy was wrong; promoting my efforts here

}

@Override
public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
return BinaryResponseParser.BINARY_CONTENT_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
import org.apache.solr.schema.StrField;
import org.apache.solr.search.ReturnFields;

/** Response writer for csv data */
public class CSVResponseWriter implements QueryResponseWriter {
/** Response writer for CSV data */
public class CSVResponseWriter implements TextQueryResponseWriter {

@Override
public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* A response writer impl that can write results in CBOR (cbor.io) format when wt=cbor. It uses the
* jackson library to write the stream out
*/
public class CborResponseWriter extends BinaryResponseWriter {
public class CborResponseWriter implements QueryResponseWriter {
final CBORFactory cborFactory;
final CBORFactory cborFactoryCompact;

Expand All @@ -40,7 +40,8 @@ public CborResponseWriter() {
}

@Override
public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response)
public void write(
OutputStream out, SolrQueryRequest req, SolrQueryResponse response, String contentType)
throws IOException {
boolean useStringRef = req.getParams().getBool("string_ref", true);
WriterImpl writer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@
import org.apache.solr.handler.GraphHandler;
import org.apache.solr.request.SolrQueryRequest;

public class GraphMLResponseWriter implements QueryResponseWriter {
/**
* Used with streaming expressions to export graphs to be visualized.
*
* @see <a href="http://graphml.graphdrawing.org/">GraphML</a>
*/
public class GraphMLResponseWriter implements TextQueryResponseWriter {

@Override
public String getContentType(SolrQueryRequest req, SolrQueryResponse res) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.ReturnFields;

/** */
public class JSONResponseWriter implements QueryResponseWriter {
/** JSON {@link QueryResponseWriter}. */
public class JSONResponseWriter implements TextQueryResponseWriter {
public static String CONTENT_TYPE_JSON_UTF8 = "application/json; charset=UTF-8";

private String contentType = CONTENT_TYPE_JSON_UTF8;
Expand Down
52 changes: 38 additions & 14 deletions solr/core/src/java/org/apache/solr/response/JacksonJsonWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import org.apache.solr.common.PushWriter;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.request.SolrQueryRequest;

/** A JSON ResponseWriter that uses jackson. */
public class JacksonJsonWriter extends BinaryResponseWriter {
public class JacksonJsonWriter implements TextQueryResponseWriter {

protected final JsonFactory jsonfactory;
protected static final DefaultPrettyPrinter pretty =
Expand All @@ -43,17 +47,42 @@ public JacksonJsonWriter() {
jsonfactory = new JsonFactory();
}

// let's also implement the binary version since Jackson supports that (probably faster)
@Override
public void write(OutputStream out, SolrQueryRequest request, SolrQueryResponse response)
public void write(
OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
throws IOException {
WriterImpl sw = new WriterImpl(jsonfactory, out, request, response);
out = new NonFlushingStream(out);
// resolve the encoding
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this charSet check logic is new. Since Jackson can specify the encoding and since the contentType (with possibly the encoding) is now passed in (new), made sense to choose the right one.

final String charSet = ContentStreamBase.getCharsetFromContentType(contentType);
JsonEncoding jsonEncoding;
if (charSet != null) {
assert JsonEncoding.values().length < 10; // fast to iterate
jsonEncoding =
Arrays.stream(JsonEncoding.values())
.filter(e -> e.getJavaName().equalsIgnoreCase(charSet))
.findAny()
.orElseThrow(() -> new UnsupportedEncodingException(charSet));
} else {
jsonEncoding = JsonEncoding.UTF8;
}

var sw = new WriterImpl(request, response, jsonfactory.createGenerator(out, jsonEncoding));
sw.writeResponse();
sw.close();
}

@Override
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response)
throws IOException {
var sw = new WriterImpl(request, response, jsonfactory.createGenerator(writer));
sw.writeResponse();
sw.close();
}

public PushWriter getWriter(
OutputStream out, SolrQueryRequest request, SolrQueryResponse response) {
return new WriterImpl(jsonfactory, out, request, response);
OutputStream out, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
return new WriterImpl(request, response, jsonfactory.createGenerator(out, JsonEncoding.UTF8));
}

@Override
Expand All @@ -67,16 +96,11 @@ public static class WriterImpl extends JSONWriter {

protected JsonGenerator gen;

public WriterImpl(
JsonFactory j, OutputStream out, SolrQueryRequest req, SolrQueryResponse rsp) {
public WriterImpl(SolrQueryRequest req, SolrQueryResponse rsp, JsonGenerator generator) {
super(null, req, rsp);
try {
gen = j.createGenerator(out, JsonEncoding.UTF8);
if (doIndent) {
gen.setPrettyPrinter(pretty.createInstance());
}
} catch (IOException e) {
throw new RuntimeException(e);
gen = generator;
if (doIndent) {
gen.setPrettyPrinter(pretty.createInstance());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@
* org.apache.solr.handler.admin.MetricsHandler}
*/
@SuppressWarnings(value = "unchecked")
public class PrometheusResponseWriter extends RawResponseWriter {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlbiscoc why did you choose RawResponseWriter? It's a very special QueryResponseWriter and I don't see that the this response writer utilizes it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully remember. I think I followed a different writer at the time and this one took an OutputStream which was what I was looking for but please with change if this is not correct.

public class PrometheusResponseWriter implements QueryResponseWriter {
// not TextQueryResponseWriter because Prometheus libs work with an OutputStream

private static final String CONTENT_TYPE_PROMETHEUS = "text/plain; version=0.0.4";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@Override
public void write(OutputStream out, SolrQueryRequest request, SolrQueryResponse response)
public void write(
OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
throws IOException {
NamedList<Object> prometheusRegistries =
(NamedList<Object>) response.getValues().get("metrics");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
*/
package org.apache.solr.response;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import net.jcip.annotations.ThreadSafe;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;

/**
* Implementations of <code>QueryResponseWriter</code> are used to format responses to query
* requests.
* Used to format responses to the client (not necessarily a "query").
*
* <p>Different <code>QueryResponseWriter</code>s are registered with the <code>SolrCore</code>. One
* way to register a QueryResponseWriter with the core is through the <code>solrconfig.xml</code>
Expand All @@ -39,23 +41,26 @@
* <p>A single instance of any registered QueryResponseWriter is created via the default constructor
* and is reused for all relevant queries.
*/
@ThreadSafe
public interface QueryResponseWriter extends NamedListInitializedPlugin {
public static String CONTENT_TYPE_XML_UTF8 = "application/xml; charset=UTF-8";
public static String CONTENT_TYPE_TEXT_UTF8 = "text/plain; charset=UTF-8";
public static String CONTENT_TYPE_TEXT_ASCII = "text/plain; charset=US-ASCII";

/**
* Write a SolrQueryResponse, this method must be thread save.
*
* <p>Information about the request (in particular: formatting options) may be obtained from
* <code>req</code> but the dominant source of information should be <code>rsp</code>.
*
* <p>There are no mandatory actions that write must perform. An empty write implementation would
* fulfill all interface obligations.
* Writes the response to the {@link OutputStream}. {@code contentType} is from {@link
* #getContentType(SolrQueryRequest, SolrQueryResponse)}, and it's often ignored.
*/
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response)
void write(
OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added contentType since the production code path will have this already, and we'll need it in TextQueryResponseWriter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a debatable change... it'd be less disruptive to leave it as it was and then only the text formats will re-acquire it which is fairly trivial parsing to do a second time. Shrug.

throws IOException;

// should be "final" if interfaces allowed that
default void write(OutputStream out, SolrQueryRequest request, SolrQueryResponse response)
throws IOException {
write(out, request, response, getContentType(request, response));
}

/**
* Return the applicable Content Type for a request, this method must be thread safe.
*
Expand All @@ -64,5 +69,13 @@ public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse res
*
* @return a Content-Type string, which may not be null.
*/
public String getContentType(SolrQueryRequest request, SolrQueryResponse response);
String getContentType(SolrQueryRequest request, SolrQueryResponse response);

/** Writes a String for debugging / testing purposes. */
default String writeToString(SolrQueryRequest request, SolrQueryResponse response)
throws IOException {
var buffer = new ByteArrayOutputStream(32000);
write(buffer, request, response);
return buffer.toString(StandardCharsets.UTF_8);
}
}
Loading
Loading