From 99a511473760c6c4628ecd638d68c107be541a00 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 13:02:34 -0500 Subject: [PATCH 01/13] Commenting out code and adding a content type --> wt converter appears to work Some failing tests for plugins like CborResponseWriter that aren't in the content type --> wt converter. --- .../org/apache/solr/core/RequestHandlers.java | 3 +- .../java/org/apache/solr/core/SolrCore.java | 3 +- .../apache/solr/request/SolrQueryRequest.java | 41 ++++++++++++++++++- .../solr/servlet/SolrRequestParsers.java | 24 +++++------ .../org/apache/solr/util/TestHarness.java | 1 + 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java index dca7c832e3fa..e20c05ced542 100644 --- a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java +++ b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java @@ -117,7 +117,8 @@ void initHandlersFromConfig(SolrConfig config) { modifiedInfos.add(applyInitParams(config, info)); } handlers.init(Collections.emptyMap(), core, modifiedInfos); - handlers.alias(handlers.getDefault(), ""); + // Curious if this is actually needed! + // handlers.alias(handlers.getDefault(), ""); if (log.isDebugEnabled()) { log.debug("Registered paths: {}", StrUtils.join(new ArrayList<>(handlers.keySet()), ',')); } diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java index 24c7dc83b0c9..6ea54daf9ff8 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -3094,7 +3094,6 @@ public PluginBag getResponseWriters() { HashMap m = new HashMap<>(15, 1); m.put("xml", new XMLResponseWriter()); m.put(CommonParams.JSON, new JacksonJsonWriter()); - m.put("standard", m.get(CommonParams.JSON)); m.put("geojson", new GeoJSONResponseWriter()); m.put("graphml", new GraphMLResponseWriter()); m.put("raw", new RawResponseWriter()); @@ -3154,7 +3153,7 @@ default String getContentType() { private void initWriters() { responseWriters.init(DEFAULT_RESPONSE_WRITERS, this); // configure the default response writer; this one should never be null - if (responseWriters.getDefault() == null) responseWriters.setDefault("standard"); + // if (responseWriters.getDefault() == null) responseWriters.setDefault("standard"); } /** Finds a writer by name, or returns the default writer if not found. */ diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java index 10115192fbaf..28a5bb9935d5 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -200,12 +200,49 @@ default QueryResponseWriter getResponseWriter() { // it's weird this method is here instead of SolrQueryResponse, but it's practical/convenient SolrCore core = getCore(); String wt = getParams().get(CommonParams.WT); + + if (wt == null || wt.isEmpty()) { + // Fall back to Accept header if wt parameter is not provided + wt = getWtFromAcceptHeader(); + } + if (core != null) { return core.getQueryResponseWriter(wt); } else { - return SolrCore.DEFAULT_RESPONSE_WRITERS.getOrDefault( - wt, SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard")); + return SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt); + } + } + + /** + * Maps the HTTP Accept header to a wt parameter value. Returns "json" as default if no Accept + * header is present or if the content type is not recognized. + * + *

This is a quick implmentation, but it's not pluggable. For example, how do we support CBOR + * without modifying this. In V2ApiUtils.getMediaTypeFromWtParam() we do the same basic thing. i + * also dont' see cbor. + * + * @return the wt parameter value corresponding to the Accept header, or "json" as default + */ + default String getWtFromAcceptHeader() { + HttpSolrCall httpSolrCall = getHttpSolrCall(); + if (httpSolrCall != null) { + String acceptHeader = httpSolrCall.getReq().getHeader("Accept"); + if (acceptHeader != null && !acceptHeader.isEmpty()) { + // Handle multiple accept types (e.g., "application/json, text/plain") + // by checking if the header contains specific content types + if (acceptHeader.contains("application/xml") || acceptHeader.contains("text/xml")) { + return "xml"; + } else if (acceptHeader.contains("application/json")) { + return CommonParams.JSON; + } else if (acceptHeader.contains("application/javabin") + || acceptHeader.contains("application/vnd.apache.solr.javabin")) { + return CommonParams.JAVABIN; + } + // For "*/*" or unrecognized types, fall through to default + } } + // Default to JSON when no Accept header or unrecognized content type + return CommonParams.JSON; } /** diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java index 3cf12aa7982b..ab82aa5905a8 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java @@ -68,11 +68,11 @@ public class SolrRequestParsers { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); // Should these constants be in a more public place? - public static final String MULTIPART = "multipart"; - public static final String FORMDATA = "formdata"; - public static final String RAW = "raw"; - public static final String SIMPLE = "simple"; - public static final String STANDARD = "standard"; + // public static final String MULTIPART = "multipart"; + // public static final String FORMDATA = "formdata"; + // public static final String RAW = "raw"; + // public static final String SIMPLE = "simple"; + // public static final String STANDARD = "standard"; private static final Charset CHARSET_US_ASCII = StandardCharsets.US_ASCII; @@ -81,7 +81,7 @@ public class SolrRequestParsers { public static final String REQUEST_TIMER_SERVLET_ATTRIBUTE = "org.apache.solr.RequestTimer"; - private final HashMap parsers = new HashMap<>(); + // private final HashMap parsers = new HashMap<>(); private StandardRequestParser standard; /** @@ -120,12 +120,12 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) { // I don't see a need to have this publicly configured just yet // adding it is trivial - parsers.put(MULTIPART, multi); - parsers.put(FORMDATA, formdata); - parsers.put(RAW, raw); - parsers.put(SIMPLE, new SimpleRequestParser()); - parsers.put(STANDARD, standard); - parsers.put("", standard); + // parsers.put(MULTIPART, multi); + // parsers.put(FORMDATA, formdata); + // parsers.put(RAW, raw); + // parsers.put(SIMPLE, new SimpleRequestParser()); + // parsers.put(STANDARD, standard); + // parsers.put("", standard); } private static RTimerTree getRequestTimer(HttpServletRequest req) { diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java index 29caa318a048..bee341b78429 100644 --- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java +++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java @@ -419,6 +419,7 @@ public LocalRequestFactory() {} @SuppressWarnings({"unchecked"}) public LocalSolrQueryRequest makeRequest(String... q) { if (q.length == 1) { + args.put("wt", "xml"); return new LocalSolrQueryRequest( TestHarness.this.getCore(), q[0], qtype, start, limit, args); } From d976a1a6e281d6035a8f974f6df999171f47043c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 14:04:26 -0500 Subject: [PATCH 02/13] don't overwrite the wt if it's been set. --- .../src/java/org/apache/solr/util/TestHarness.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java index bee341b78429..a933605466fc 100644 --- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java +++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java @@ -419,7 +419,7 @@ public LocalRequestFactory() {} @SuppressWarnings({"unchecked"}) public LocalSolrQueryRequest makeRequest(String... q) { if (q.length == 1) { - args.put("wt", "xml"); + args.computeIfAbsent("wt", k -> "xml"); return new LocalSolrQueryRequest( TestHarness.this.getCore(), q[0], qtype, start, limit, args); } From 63d428485187eff43253ccdb9b7cbf3a722bd049 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 14:09:41 -0500 Subject: [PATCH 03/13] Change the logic so that if you use a bad wt then instead of a 400 not found you get a 500 error. I think using a bad responsewriter should be an error, and we shoudln't cover it up, or return json... --- .../apache/solr/request/SolrQueryRequest.java | 11 ++++++-- .../solr/handler/V2ApiIntegrationTest.java | 25 ++++++++++--------- .../TestPrometheusResponseWriter.java | 3 ++- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java index 28a5bb9935d5..2c7480e2c2b7 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.cloud.CloudDescriptor; +import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.CommandOperation; @@ -206,11 +207,17 @@ default QueryResponseWriter getResponseWriter() { wt = getWtFromAcceptHeader(); } + QueryResponseWriter writer; if (core != null) { - return core.getQueryResponseWriter(wt); + writer = core.getQueryResponseWriter(wt); } else { - return SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt); + writer = SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt); } + if (writer == null) { + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, "Unknown response writer type: " + wt); + } + return writer; } /** diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java index 3b7a85eb0967..cbd12f8a99b4 100644 --- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java +++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java @@ -114,26 +114,27 @@ public void testIntrospect() throws Exception { } @Test - public void testWTParam() throws Exception { + public void testInvalidWTParamReturnsError() throws Exception { V2Request request = new V2Request.Builder("/c/" + COLL_NAME + "/get/_introspect").build(); - // TODO: If possible do this in a better way + // Using an invalid wt parameter should return a 500 error request.setResponseParser(new InputStreamResponseParser("bleh")); NamedList res = cluster.getSolrClient().request(request); String respString = InputStreamResponseParser.consumeResponseToString(res); - assertFalse(respString.contains("

HTTP ERROR 500

")); - assertFalse(respString.contains("500")); - assertFalse(respString.contains("NullPointerException")); - assertFalse( - respString.contains( - "

Problem accessing /solr/____v2/c/collection1/get/_introspect. Reason:")); - // since no-op response writer is used, doing contains match - assertTrue(respString.contains("/c/collection1/get")); + // Should get a 500 Server Error for unknown writer type + assertTrue( + "Expected error message about unknown writer type", + respString.contains("Unknown response writer type")); + assertTrue("Expected 500 error code", respString.contains("500")); + } - // no response parser + @Test + public void testWTParam() throws Exception { + // When no response parser is set, the default JSON writer should be used + V2Request request = new V2Request.Builder("/c/" + COLL_NAME + "/get/_introspect").build(); request.setResponseParser(null); Map resp = resAsMap(cluster.getSolrClient(), request); - respString = resp.toString(); + String respString = resp.toString(); assertFalse(respString.contains("

HTTP ERROR 500

")); assertFalse( diff --git a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java index d9d2493eb3a1..63ba5a448923 100644 --- a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java +++ b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java @@ -195,7 +195,8 @@ public void testUnsupportedMetricsFormat() throws Exception { try (SolrClient adminClient = getHttpSolrClient(solrTestRule.getBaseUrl())) { NamedList res = adminClient.request(req); - assertEquals(400, res.get("responseStatus")); + // Unknown wt parameter should return a 500 error + assertEquals(500, res.get("responseStatus")); } } } From 90dd9871b8a693949506a48fb929ec6022cee2ba Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 16:28:31 -0500 Subject: [PATCH 04/13] json is the new standard --- solr/core/src/test/org/apache/solr/OutputWriterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/core/src/test/org/apache/solr/OutputWriterTest.java b/solr/core/src/test/org/apache/solr/OutputWriterTest.java index 30df9ed98fab..7aa332653ba1 100644 --- a/solr/core/src/test/org/apache/solr/OutputWriterTest.java +++ b/solr/core/src/test/org/apache/solr/OutputWriterTest.java @@ -44,7 +44,7 @@ public static void beforeClass() throws Exception { @Test public void testSOLR59responseHeaderVersions() { // default results in "new" responseHeader - lrf.args.put("wt", "standard"); + lrf.args.put("wt", "json"); assertQ(req("foo"), "/response/lst[@name='responseHeader']/int[@name='status'][.='0']"); lrf.args.remove("wt"); assertQ(req("foo"), "/response/lst[@name='responseHeader']/int[@name='QTime']"); From 947464b13c4bfab1a68e9deef453365bda25c093 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 17:32:56 -0500 Subject: [PATCH 05/13] No longer supporting a "default" since we specify what we want everywhere. --- .../java/org/apache/solr/core/SolrCore.java | 4 ++-- .../solr/response/TestRawResponseWriter.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java index 6ea54daf9ff8..b204d0d5c3fd 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -3156,9 +3156,9 @@ private void initWriters() { // if (responseWriters.getDefault() == null) responseWriters.setDefault("standard"); } - /** Finds a writer by name, or returns the default writer if not found. */ + /** Finds a writer by name, or null if not found. */ public final QueryResponseWriter getQueryResponseWriter(String writerName) { - return responseWriters.get(writerName, true); + return responseWriters.get(writerName, false); } /** diff --git a/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java index 7b822b18848d..8799277bddde 100644 --- a/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java +++ b/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java @@ -44,7 +44,7 @@ public class TestRawResponseWriter extends SolrTestCaseJ4 { private static RawResponseWriter writerJsonBase; private static RawResponseWriter writerBinBase; private static RawResponseWriter writerCborBase; - private static RawResponseWriter writerNoBase; + // private static RawResponseWriter writerNoBase; private static RawResponseWriter[] allWriters; @@ -56,16 +56,14 @@ public static void setupCoreAndWriters() throws Exception { // we spin up. initCore("solrconfig.xml", "schema.xml"); - writerNoBase = newRawResponseWriter(null); /* defaults to standard writer as base */ + // writerNoBase = newRawResponseWriter(null); /* defaults to standard writer as base */ writerXmlBase = newRawResponseWriter("xml"); writerJsonBase = newRawResponseWriter("json"); writerBinBase = newRawResponseWriter("javabin"); writerCborBase = newRawResponseWriter("cbor"); allWriters = - new RawResponseWriter[] { - writerXmlBase, writerJsonBase, writerBinBase, writerCborBase, writerNoBase - }; + new RawResponseWriter[] {writerXmlBase, writerJsonBase, writerBinBase, writerCborBase}; } @AfterClass @@ -73,7 +71,7 @@ public static void cleanupWriters() { writerXmlBase = null; writerJsonBase = null; writerBinBase = null; - writerNoBase = null; + // writerNoBase = null; writerCborBase = null; allWriters = null; } @@ -120,7 +118,7 @@ public void testRawStringContentStream() throws IOException { // we should have UTF-8 Bytes if we use an OutputStream ByteArrayOutputStream bout = new ByteArrayOutputStream(); writer.write(bout, req(), rsp); - assertEquals(data, bout.toString(StandardCharsets.UTF_8.toString())); + assertEquals(data, bout.toString(StandardCharsets.UTF_8)); } } @@ -133,7 +131,7 @@ public void testStructuredDataViaBaseWriters() throws IOException { rsp.add("foo", "bar"); // check Content-Type against each writer - assertEquals("application/xml; charset=UTF-8", writerNoBase.getContentType(req(), rsp)); + // assertEquals("application/xml; charset=UTF-8", writerNoBase.getContentType(req(), rsp)); assertEquals("application/xml; charset=UTF-8", writerXmlBase.getContentType(req(), rsp)); assertEquals("application/json; charset=UTF-8", writerJsonBase.getContentType(req(), rsp)); assertEquals("application/octet-stream", writerBinBase.getContentType(req(), rsp)); @@ -153,13 +151,13 @@ public void testStructuredDataViaBaseWriters() throws IOException { assertEquals(xml, writerXmlBase.writeToString(req(), rsp)); ByteArrayOutputStream xmlBout = new ByteArrayOutputStream(); writerXmlBase.write(xmlBout, req(), rsp); - assertEquals(xml, xmlBout.toString(StandardCharsets.UTF_8.toString())); + assertEquals(xml, xmlBout.toString(StandardCharsets.UTF_8)); // - assertEquals(xml, writerNoBase.writeToString(req(), rsp)); - ByteArrayOutputStream noneBout = new ByteArrayOutputStream(); - writerNoBase.write(noneBout, req(), rsp); - assertEquals(xml, noneBout.toString(StandardCharsets.UTF_8.toString())); + // assertEquals(xml, writerNoBase.writeToString(req(), rsp)); + // ByteArrayOutputStream noneBout = new ByteArrayOutputStream(); + // writerNoBase.write(noneBout, req(), rsp); + // assertEquals(xml, noneBout.toString(StandardCharsets.UTF_8.toString())); // json String json = "{\n" + " \"content\":\"test\",\n" + " \"foo\":\"bar\"}\n"; From c76ad676f35d637c109abafbc148322db69a7cb0 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 31 Jan 2026 15:29:30 -0500 Subject: [PATCH 06/13] fix merge --- .../apache/solr/request/SolrQueryRequest.java | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java index 5fd7da8dae12..0ce4d82e5516 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -23,7 +23,6 @@ import java.util.Map; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.cloud.CloudDescriptor; -import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.CommandOperation; @@ -213,50 +212,6 @@ default QueryResponseWriter getResponseWriter() { } else { return ResponseWritersRegistry.getWriter(wt); } - - QueryResponseWriter writer; - if (core != null) { - writer = core.getQueryResponseWriter(wt); - } else { - writer = SolrCore.DEFAULT_RESPONSE_WRITERS.get(wt); - } - if (writer == null) { - throw new SolrException( - SolrException.ErrorCode.SERVER_ERROR, "Unknown response writer type: " + wt); - } - return writer; - } - - /** - * Maps the HTTP Accept header to a wt parameter value. Returns "json" as default if no Accept - * header is present or if the content type is not recognized. - * - *

This is a quick implmentation, but it's not pluggable. For example, how do we support CBOR - * without modifying this. In V2ApiUtils.getMediaTypeFromWtParam() we do the same basic thing. i - * also dont' see cbor. - * - * @return the wt parameter value corresponding to the Accept header, or "json" as default - */ - default String getWtFromAcceptHeader() { - HttpSolrCall httpSolrCall = getHttpSolrCall(); - if (httpSolrCall != null) { - String acceptHeader = httpSolrCall.getReq().getHeader("Accept"); - if (acceptHeader != null && !acceptHeader.isEmpty()) { - // Handle multiple accept types (e.g., "application/json, text/plain") - // by checking if the header contains specific content types - if (acceptHeader.contains("application/xml") || acceptHeader.contains("text/xml")) { - return "xml"; - } else if (acceptHeader.contains("application/json")) { - return CommonParams.JSON; - } else if (acceptHeader.contains("application/javabin") - || acceptHeader.contains("application/vnd.apache.solr.javabin")) { - return CommonParams.JAVABIN; - } - // For "*/*" or unrecognized types, fall through to default - } - } - // Default to JSON when no Accept header or unrecognized content type - return CommonParams.JSON; } /** From c9e9beae1e247889cabffe208074a4cbbe536365 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 3 Feb 2026 16:46:29 -0500 Subject: [PATCH 07/13] lots of review and rethinking --- changelog/unreleased/SOLR-18085.yml | 8 ++ .../java/org/apache/solr/core/PluginBag.java | 2 +- .../org/apache/solr/core/RequestHandlers.java | 3 +- .../java/org/apache/solr/core/SolrCore.java | 3 - .../apache/solr/request/SolrQueryRequest.java | 9 +- .../response/ResponseWritersRegistry.java | 90 ++++++++++++++----- .../org/apache/solr/servlet/HttpSolrCall.java | 2 +- .../solr/servlet/SolrRequestParsers.java | 43 +++------ .../solr/handler/V2ApiIntegrationTest.java | 8 +- .../response/TestResponseWritersRegistry.java | 20 ++--- 10 files changed, 105 insertions(+), 83 deletions(-) create mode 100644 changelog/unreleased/SOLR-18085.yml diff --git a/changelog/unreleased/SOLR-18085.yml b/changelog/unreleased/SOLR-18085.yml new file mode 100644 index 000000000000..3d618fdeedf3 --- /dev/null +++ b/changelog/unreleased/SOLR-18085.yml @@ -0,0 +1,8 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Removed the wt=standard concept that was used internally by Solr. It was redundent with json writer type that was used as well. +type: removed # added, changed, fixed, deprecated, removed, dependency_update, security, other +authors: + - name: Eric Pugh +links: + - name: SOLR-18085 + url: https://issues.apache.org/jira/browse/SOLR-18085 diff --git a/solr/core/src/java/org/apache/solr/core/PluginBag.java b/solr/core/src/java/org/apache/solr/core/PluginBag.java index 7624a4c13ddd..1af3445e7aaa 100644 --- a/solr/core/src/java/org/apache/solr/core/PluginBag.java +++ b/solr/core/src/java/org/apache/solr/core/PluginBag.java @@ -202,7 +202,7 @@ public T get(String name) { * Fetches a plugin by name , or the default * * @param name name using which it is registered - * @param useDefault Return the default , if a plugin by that name does not exist + * @param useDefault Return the default, if a plugin by that name does not exist */ public T get(String name, boolean useDefault) { T result = get(name); diff --git a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java index e20c05ced542..39f043d95f90 100644 --- a/solr/core/src/java/org/apache/solr/core/RequestHandlers.java +++ b/solr/core/src/java/org/apache/solr/core/RequestHandlers.java @@ -117,8 +117,7 @@ void initHandlersFromConfig(SolrConfig config) { modifiedInfos.add(applyInitParams(config, info)); } handlers.init(Collections.emptyMap(), core, modifiedInfos); - // Curious if this is actually needed! - // handlers.alias(handlers.getDefault(), ""); + if (log.isDebugEnabled()) { log.debug("Registered paths: {}", StrUtils.join(new ArrayList<>(handlers.keySet()), ',')); } diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java index eabbbf3b7648..c936dda686f1 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -3131,9 +3131,6 @@ private void initWriters() { // Initialize with the built defaults responseWriters.init(defaultWriters, this); - - // configure the default response writer; this one should never be null - // if (responseWriters.getDefault() == null) responseWriters.setDefault("standard"); } /** Finds a writer by name, or null if not found. */ diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java index 0ce4d82e5516..c4aecf0c47ed 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -204,14 +204,7 @@ default CloudDescriptor getCloudDescriptor() { */ default QueryResponseWriter getResponseWriter() { // it's weird this method is here instead of SolrQueryResponse, but it's practical/convenient - SolrCore core = getCore(); - String wt = getParams().get(CommonParams.WT); - // Use core writers if available, otherwise fall back to built-in writers - if (core != null) { - return core.getQueryResponseWriter(wt); - } else { - return ResponseWritersRegistry.getWriter(wt); - } + return ResponseWritersRegistry.getWriter(getParams().get(CommonParams.WT), getCore()); } /** diff --git a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index cfd04e28714e..08d4568440b4 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -20,7 +20,9 @@ import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT; import java.util.Map; +import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; +import org.apache.solr.core.SolrCore; import org.apache.solr.handler.admin.api.ReplicationAPIBase; /** @@ -46,21 +48,14 @@ private ResponseWritersRegistry() { PrometheusResponseWriter prometheusWriter = new PrometheusResponseWriter(); BUILTIN_WRITERS = - Map.of( - CommonParams.JAVABIN, - new JavaBinResponseWriter(), - CommonParams.JSON, - jsonWriter, - "standard", - jsonWriter, // Alias for JSON - "xml", - new XMLResponseWriter(), - PROMETHEUS_METRICS_WT, - prometheusWriter, - OPEN_METRICS_WT, - prometheusWriter, - ReplicationAPIBase.FILE_STREAM, - new FileStreamResponseWriter()); + Map.ofEntries( + Map.entry(CommonParams.JAVABIN, new JavaBinResponseWriter()), + Map.entry(CommonParams.JSON, jsonWriter), + Map.entry("xml", new XMLResponseWriter()), + Map.entry("raw", new RawResponseWriter()), + Map.entry(PROMETHEUS_METRICS_WT, prometheusWriter), + Map.entry(OPEN_METRICS_WT, prometheusWriter), + Map.entry(ReplicationAPIBase.FILE_STREAM, new FileStreamResponseWriter())); } /** @@ -69,17 +64,72 @@ private ResponseWritersRegistry() { *

Built-in writers are always available and provide essential formats needed by admin APIs and * core functionality. They do not depend on core configuration or ImplicitPlugins.json settings. * - *

If the requested writer is not available, returns the "standard" (JSON) writer as a - * fallback. This ensures requests always get a valid response format. + *

If the requested writer is not available, returns the JSON writer as a fallback. This + * ensures requests always get a valid response format. * * @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default - * @return the response writer, never null (returns "standard"/JSON if not found) + * @return the response writer, never null (returns JSON if not found) */ public static QueryResponseWriter getWriter(String writerName) { if (writerName == null || writerName.isEmpty()) { - return BUILTIN_WRITERS.get("standard"); + return BUILTIN_WRITERS.get(CommonParams.JSON); } - return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard")); + return BUILTIN_WRITERS.get(writerName); + } + + /** + * Gets a response writer, trying the core's registry first, then falling back to built-in + * writers. This is the unified entry point for all writer resolution. + * + *

Resolution order: + * + *

    + *
  1. If core is provided, check core's writer registry + *
  2. If not found in core (or no core), check built-in writers + *
  3. If writer name is explicitly specified but not found anywhere, throw exception + *
  4. If writer name is null/empty, return default (JSON) + *
+ * + * @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default + * @param core the SolrCore to check first, or null for node-level requests + * @return the response writer, never null + * @throws SolrException if an explicitly requested writer type is not found + */ + public static QueryResponseWriter getWriter(String writerName, SolrCore core) { + QueryResponseWriter writer = null; + + // Try core registry first if available + if (core != null) { + writer = core.getQueryResponseWriter(writerName); + } + + // If not found and writer is explicitly requested, validate it exists in built-in + if (writer == null && writerName != null && !writerName.isEmpty()) { + if (!hasWriter(writerName)) { + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, "Unknown response writer type: " + writerName); + } + } + + // Fallback to built-in writers (or default if writerName is null/empty) + if (writer == null) { + writer = getWriter(writerName); + } + + return writer; + } + + /** + * Checks if a writer with the given name exists in the built-in writers. + * + * @param writerName the writer name to check + * @return true if the writer exists, false otherwise + */ + public static boolean hasWriter(String writerName) { + if (writerName == null || writerName.isEmpty()) { + return true; // null/empty is valid, will use default + } + return BUILTIN_WRITERS.containsKey(writerName); } /** diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java index 1229aed8d0a0..b73da05e0bfd 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -727,7 +727,7 @@ private void handleAdminRequest() throws IOException { protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOException { if (solrResp.getToLog().size() > 0) { - // has to come second and in it's own if to keep ./gradlew check happy. + // has to come second and in its own "if" to keep ./gradlew check happy. if (log.isInfoEnabled()) { log.info( handler != null diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java index ab82aa5905a8..2fee5059f3dc 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java @@ -67,13 +67,6 @@ public class SolrRequestParsers { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - // Should these constants be in a more public place? - // public static final String MULTIPART = "multipart"; - // public static final String FORMDATA = "formdata"; - // public static final String RAW = "raw"; - // public static final String SIMPLE = "simple"; - // public static final String STANDARD = "standard"; - private static final Charset CHARSET_US_ASCII = StandardCharsets.US_ASCII; public static final String INPUT_ENCODING_KEY = "ie"; @@ -81,8 +74,7 @@ public class SolrRequestParsers { public static final String REQUEST_TIMER_SERVLET_ATTRIBUTE = "org.apache.solr.RequestTimer"; - // private final HashMap parsers = new HashMap<>(); - private StandardRequestParser standard; + private StandardRequestParser parser; /** * Default instance for e.g. admin requests. Limits to 2 MB uploads and does not allow remote @@ -91,7 +83,7 @@ public class SolrRequestParsers { public static final SolrRequestParsers DEFAULT = new SolrRequestParsers(); /** - * Pass in an xml configuration. A null configuration will enable everything with maximum values. + * Pass in a xml configuration. A null configuration will enable everything with maximum values. */ public SolrRequestParsers(SolrConfig globalConfig) { final int multipartUploadLimitKB, formUploadLimitKB; @@ -116,7 +108,7 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) { MultipartRequestParser multi = new MultipartRequestParser(multipartUploadLimitKB); RawRequestParser raw = new RawRequestParser(); FormDataRequestParser formdata = new FormDataRequestParser(formUploadLimitKB); - standard = new StandardRequestParser(multi, raw, formdata); + parser = new StandardRequestParser(multi, raw, formdata); // I don't see a need to have this publicly configured just yet // adding it is trivial @@ -130,7 +122,7 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) { private static RTimerTree getRequestTimer(HttpServletRequest req) { final Object reqTimer = req.getAttribute(REQUEST_TIMER_SERVLET_ATTRIBUTE); - if (reqTimer != null && reqTimer instanceof RTimerTree) { + if (reqTimer instanceof RTimerTree) { return ((RTimerTree) reqTimer); } @@ -139,7 +131,6 @@ private static RTimerTree getRequestTimer(HttpServletRequest req) { public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req) throws Exception { - SolrRequestParser parser = standard; // TODO -- in the future, we could pick a different parser based on the request @@ -164,13 +155,12 @@ public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req /** For embedded Solr use; not related to HTTP. */ public SolrQueryRequest buildRequestFrom( - SolrCore core, SolrParams params, Collection streams) throws Exception { + SolrCore core, SolrParams params, Collection streams) { return buildRequestFrom(core, params, streams, new RTimerTree(), null, null); } public SolrQueryRequest buildRequestFrom( - SolrCore core, SolrParams params, Collection streams, Principal principal) - throws Exception { + SolrCore core, SolrParams params, Collection streams, Principal principal) { return buildRequestFrom(core, params, streams, new RTimerTree(), null, principal); } @@ -181,7 +171,7 @@ private SolrQueryRequest buildRequestFrom( RTimerTree requestTimer, final HttpServletRequest req, final Principal principal) // from req, if req was provided, otherwise from elsewhere - throws Exception { + { // ensure streams is non-null and mutable so we can easily add to it if (streams == null) { streams = new ArrayList<>(); @@ -213,7 +203,7 @@ public List getCommands(boolean validateInput) { @Override public Map getPathTemplateValues() { - if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) { + if (httpSolrCall instanceof V2HttpCall) { return ((V2HttpCall) httpSolrCall).getUrlParts(); } return super.getPathTemplateValues(); @@ -457,7 +447,7 @@ private static int digit16(int b) { // ----------------------------------------------------------------- // ----------------------------------------------------------------- - // I guess we don't really even need the interface, but i'll keep it here just for kicks + // I guess we don't really even need the interface, but I'll keep it here just for kicks interface SolrRequestParser { public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList streams) throws Exception; @@ -466,15 +456,6 @@ public SolrParams parseParamsAndFillStreams( // ----------------------------------------------------------------- // ----------------------------------------------------------------- - /** The simple parser just uses the params directly, does not support POST URL-encoded forms */ - static class SimpleRequestParser implements SolrRequestParser { - @Override - public SolrParams parseParamsAndFillStreams( - final HttpServletRequest req, ArrayList streams) throws Exception { - return parseQueryString(req.getQueryString()); - } - } - /** Wrap an HttpServletRequest as a ContentStream */ static class HttpRequestContentStream extends ContentStreamBase { private final InputStream inputStream; @@ -515,7 +496,7 @@ public SolrParams parseParamsAndFillStreams( || req.getHeader("Transfer-Encoding") != null || !NO_BODY_METHODS.contains(req.getMethod())) { // If Content-Length > 0 OR Transfer-Encoding exists OR - // it's a method that can have a body (POST/PUT/PATCH etc) + // it's a method that can have a body (POST/PUT/PATCH etc.) streams.add(new HttpRequestContentStream(req, req.getInputStream())); } @@ -543,7 +524,7 @@ public SolrParams parseParamsAndFillStreams( throw new SolrException( ErrorCode.BAD_REQUEST, "Not multipart content! " + req.getContentType()); } - // Magic way to tell Jetty dynamically we want multi-part processing. + // Magic way to tell Jetty dynamically we want multipart processing. // This is taken from: // https://github.com/eclipse/jetty.project/blob/jetty-10.0.12/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java#L144 req.setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement); @@ -735,7 +716,7 @@ static class StandardRequestParser implements SolrRequestParser { public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList streams) throws Exception { String contentType = req.getContentType(); - String method = req.getMethod(); // No need to uppercase... HTTP verbs are case sensitive + String method = req.getMethod(); // No need to uppercase... HTTP verbs are case-sensitive String uri = req.getRequestURI(); boolean isV2 = getHttpSolrCall(req) instanceof V2HttpCall; boolean isPost = "POST".equals(method); diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java index cbd12f8a99b4..f58e8994107f 100644 --- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java +++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java @@ -48,7 +48,7 @@ import org.junit.Test; public class V2ApiIntegrationTest extends SolrCloudTestCase { - private static String COLL_NAME = "collection1"; + private static final String COLL_NAME = "collection1"; @BeforeClass public static void createCluster() throws Exception { @@ -80,11 +80,7 @@ private void testException( .build(); v2Request.setResponseParser(responseParser); RemoteSolrException ex = - expectThrows( - RemoteSolrException.class, - () -> { - v2Request.process(cluster.getSolrClient()); - }); + expectThrows(RemoteSolrException.class, () -> v2Request.process(cluster.getSolrClient())); assertEquals(expectedCode, ex.code()); } diff --git a/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java b/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java index 695ad0e1278c..8fc7b178571b 100644 --- a/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java +++ b/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java @@ -31,34 +31,32 @@ public class TestResponseWritersRegistry extends SolrTestCaseJ4 { @Test public void testBuiltInWriterFallbackBehavior() { - QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("standard"); + QueryResponseWriter defaultWriter = ResponseWritersRegistry.getWriter("json"); // Test null fallback QueryResponseWriter nullWriter = ResponseWritersRegistry.getWriter(null); - assertThat("null writer should not be null", nullWriter, is(not(nullValue()))); - assertThat("null writer should be same as standard", nullWriter, is(standardWriter)); + assertThat("null writer should be same as default", nullWriter, is(defaultWriter)); // Test empty string fallback QueryResponseWriter emptyWriter = ResponseWritersRegistry.getWriter(""); assertThat("empty writer should not be null", emptyWriter, is(not(nullValue()))); - assertThat("empty writer should be same as standard", emptyWriter, is(standardWriter)); + assertThat("empty writer should be same as default", emptyWriter, is(defaultWriter)); // Test unknown format fallback QueryResponseWriter unknownWriter = ResponseWritersRegistry.getWriter("nonexistent"); - assertThat("unknown writer should not be null", unknownWriter, is(not(nullValue()))); - assertThat("unknown writer should be same as standard", unknownWriter, is(standardWriter)); + assertThat("unknown writer should be null", unknownWriter, is(nullValue())); } @Test public void testBuiltInWriterLimitedSet() { - QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("standard"); + QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("json"); // Built-in writers should NOT include extended format writers (csv, geojson, etc.) - // These should all fall back to standard - // I think this standard thing is weird... I think it should throw an exception! + // These should all fall back to json + // I think this json thing is weird... I think it should throw an exception! assertThat( - "geojson should fall back to standard", + "geojson should fall back to json", ResponseWritersRegistry.getWriter("geojson"), - is(standardWriter)); + is(nullValue())); } } From 5194e8b19d4228ad3ea81d7fb3eff2d51e2f9461 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 3 Feb 2026 19:50:35 -0500 Subject: [PATCH 08/13] use the correct name of this bats test --- solr/packaging/test/test_rolling_upgrade.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/packaging/test/test_rolling_upgrade.bats b/solr/packaging/test/test_rolling_upgrade.bats index 3902189eeb3f..ae956a868ba4 100644 --- a/solr/packaging/test/test_rolling_upgrade.bats +++ b/solr/packaging/test/test_rolling_upgrade.bats @@ -19,7 +19,7 @@ load bats_helper # You can test alternative images via # export SOLR_BEGIN_IMAGE="apache/solr-nightly:9.9.0-slim" and then running -# ./gradlw iTest --tests test_docker_solrcloud.bats +# ./gradlew iTest --tests test_rolling_upgrade.bats SOLR_BEGIN_IMAGE="${SOLR_BEGIN_IMAGE:-apache/solr-nightly:9.10.0-SNAPSHOT-slim}" SOLR_END_IMAGE="${SOLR_END_IMAGE:-apache/solr-nightly:10.0.0-SNAPSHOT-slim}" From 8cbba6a7585c8fa72180ea59a1bf8ddf2eb5ff40 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 3 Feb 2026 19:51:18 -0500 Subject: [PATCH 09/13] Reviewing PR, looking to remove commented code and simplify some logic --- .../solr/response/ResponseWritersRegistry.java | 10 +++------- .../org/apache/solr/servlet/SolrRequestParsers.java | 13 ++----------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index 08d4568440b4..34d9a509bc40 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -104,18 +104,14 @@ public static QueryResponseWriter getWriter(String writerName, SolrCore core) { } // If not found and writer is explicitly requested, validate it exists in built-in - if (writer == null && writerName != null && !writerName.isEmpty()) { + if (writer == null) { if (!hasWriter(writerName)) { throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Unknown response writer type: " + writerName); + } else { + writer = getWriter(writerName); } } - - // Fallback to built-in writers (or default if writerName is null/empty) - if (writer == null) { - writer = getWriter(writerName); - } - return writer; } diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java index 2fee5059f3dc..e835d844db86 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java @@ -109,15 +109,6 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) { RawRequestParser raw = new RawRequestParser(); FormDataRequestParser formdata = new FormDataRequestParser(formUploadLimitKB); parser = new StandardRequestParser(multi, raw, formdata); - - // I don't see a need to have this publicly configured just yet - // adding it is trivial - // parsers.put(MULTIPART, multi); - // parsers.put(FORMDATA, formdata); - // parsers.put(RAW, raw); - // parsers.put(SIMPLE, new SimpleRequestParser()); - // parsers.put(STANDARD, standard); - // parsers.put("", standard); } private static RTimerTree getRequestTimer(HttpServletRequest req) { @@ -327,9 +318,9 @@ static long parseFormDataContent( // we have no charset decoder until now, buffer the keys / values for later // processing: buffer.add(keyBytes); - buffer.add(Long.valueOf(keyPos)); + buffer.add(keyPos); buffer.add(valueBytes); - buffer.add(Long.valueOf(valuePos)); + buffer.add(valuePos); } else { // we already have a charsetDecoder, so we can directly decode without buffering: final String key = decodeChars(keyBytes, keyPos, charsetDecoder), From 53b11b00e37afbcc870cdc9c3997c2e9ecdb1ef9 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 4 Feb 2026 06:58:32 -0500 Subject: [PATCH 10/13] Update changelog for SOLR-18085 Clarified the changelog entry by removing redundant information. --- changelog/unreleased/SOLR-18085.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR-18085.yml b/changelog/unreleased/SOLR-18085.yml index 3d618fdeedf3..d6ca801ddc8e 100644 --- a/changelog/unreleased/SOLR-18085.yml +++ b/changelog/unreleased/SOLR-18085.yml @@ -1,5 +1,5 @@ # See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc -title: Removed the wt=standard concept that was used internally by Solr. It was redundent with json writer type that was used as well. +title: Removed the wt=standard concept that was used internally by Solr. type: removed # added, changed, fixed, deprecated, removed, dependency_update, security, other authors: - name: Eric Pugh From 441ae7a399dd37b0b684db7dcaef99a7ac2a0f53 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 4 Feb 2026 18:09:28 -0500 Subject: [PATCH 11/13] back out previous chnage --- .../java/org/apache/solr/response/ResponseWritersRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index a9c015f4c462..12d860cad394 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -72,7 +72,7 @@ private ResponseWritersRegistry() { */ public static QueryResponseWriter getWriter(String writerName) { if (writerName == null || writerName.isEmpty()) { - return BUILTIN_WRITERS.get(CommonParams.JSON); + writerName = CommonParams.JSON; } return BUILTIN_WRITERS.get(writerName); } From 37bdfabf08f741677b72387bdbf28c8d5cc63e5f Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 4 Feb 2026 19:17:32 -0500 Subject: [PATCH 12/13] Kill meaningless test, and tweak our UselessOutputWriter testSOLR59responseHeaderVersions looks like it tests wt, but the assertQ always sets wt=xml, so it doesn't do anything. We test qtime and status plenty of other places. I was confused by USELESS_OUTPUT till I realized it was part of UselessOutputWriter. --- .../org/apache/solr/OutputWriterTest.java | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/OutputWriterTest.java b/solr/core/src/test/org/apache/solr/OutputWriterTest.java index 7aa332653ba1..7f711631ce17 100644 --- a/solr/core/src/test/org/apache/solr/OutputWriterTest.java +++ b/solr/core/src/test/org/apache/solr/OutputWriterTest.java @@ -29,32 +29,16 @@ /** Tests the ability to configure multiple query output writers, and select those at query time. */ public class OutputWriterTest extends SolrTestCaseJ4 { - /** The XML string that's output for testing purposes. */ - public static final String USELESS_OUTPUT = "useless output"; - @BeforeClass public static void beforeClass() throws Exception { initCore("solr/crazy-path-to-config.xml", "solr/crazy-path-to-schema.xml"); } - /** - * responseHeader has changed in SOLR-59, check old and new variants, In SOLR-2413, we removed - * support for the deprecated versions - */ - @Test - public void testSOLR59responseHeaderVersions() { - // default results in "new" responseHeader - lrf.args.put("wt", "json"); - assertQ(req("foo"), "/response/lst[@name='responseHeader']/int[@name='status'][.='0']"); - lrf.args.remove("wt"); - assertQ(req("foo"), "/response/lst[@name='responseHeader']/int[@name='QTime']"); - } - @Test public void testUselessWriter() throws Exception { lrf.args.put("wt", "useless"); String out = h.query(req("foo")); - assertEquals(USELESS_OUTPUT, out); + assertEquals(UselessOutputWriter.USELESS_OUTPUT, out); } public void testLazy() { @@ -71,6 +55,9 @@ public void testLazy() { /** An output writer that doesn't do anything useful. */ public static class UselessOutputWriter implements TextQueryResponseWriter { + /** The XML string that's output for testing purposes. */ + public static final String USELESS_OUTPUT = "useless output"; + public UselessOutputWriter() {} @Override From efbb00732b55258dc24ec27761823a7f79437813 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 4 Feb 2026 19:36:33 -0500 Subject: [PATCH 13/13] I dont think this is right but cant prove it. I dont think we need a null raw response writer --- .../solr/response/RawResponseWriter.java | 9 ++++--- .../solr/response/TestRawResponseWriter.java | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java b/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java index 922b49329525..fe5269497ff1 100644 --- a/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java +++ b/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java @@ -47,7 +47,7 @@ public class RawResponseWriter implements QueryResponseWriter { */ public static final String CONTENT = "content"; - private String _baseWriter = null; + private String baseWriter = null; /** * A fallback writer used for requests that don't return raw content and that aren't associated @@ -62,14 +62,17 @@ public void init(NamedList n) { if (n != null) { Object base = n.get("base"); if (base != null) { - _baseWriter = base.toString(); + baseWriter = base.toString(); } } } protected QueryResponseWriter getBaseWriter(SolrQueryRequest request) { if (request.getCore() != null) { - return request.getCore().getQueryResponseWriter(_baseWriter); + // When baseWriter is null, use the core's default writer (useDefault=true) + // Otherwise, look up the specific writer by name (useDefault=false for explicit lookups) + boolean useDefault = (baseWriter == null); + return request.getCore().getResponseWriters().get(baseWriter, useDefault); } // Requests to a specific core already have writers, but we still need a 'default writer' for diff --git a/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java index 8799277bddde..34e006b727ba 100644 --- a/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java +++ b/solr/core/src/test/org/apache/solr/response/TestRawResponseWriter.java @@ -44,7 +44,7 @@ public class TestRawResponseWriter extends SolrTestCaseJ4 { private static RawResponseWriter writerJsonBase; private static RawResponseWriter writerBinBase; private static RawResponseWriter writerCborBase; - // private static RawResponseWriter writerNoBase; + private static RawResponseWriter writerNoBase; private static RawResponseWriter[] allWriters; @@ -56,14 +56,18 @@ public static void setupCoreAndWriters() throws Exception { // we spin up. initCore("solrconfig.xml", "schema.xml"); - // writerNoBase = newRawResponseWriter(null); /* defaults to standard writer as base */ + writerNoBase = + newRawResponseWriter( + null); /* null base uses core's default writer (XML for this core), or JSON if no core */ writerXmlBase = newRawResponseWriter("xml"); writerJsonBase = newRawResponseWriter("json"); writerBinBase = newRawResponseWriter("javabin"); writerCborBase = newRawResponseWriter("cbor"); allWriters = - new RawResponseWriter[] {writerXmlBase, writerJsonBase, writerBinBase, writerCborBase}; + new RawResponseWriter[] { + writerXmlBase, writerJsonBase, writerBinBase, writerCborBase, writerNoBase + }; } @AfterClass @@ -71,7 +75,7 @@ public static void cleanupWriters() { writerXmlBase = null; writerJsonBase = null; writerBinBase = null; - // writerNoBase = null; + writerNoBase = null; writerCborBase = null; allWriters = null; } @@ -131,7 +135,7 @@ public void testStructuredDataViaBaseWriters() throws IOException { rsp.add("foo", "bar"); // check Content-Type against each writer - // assertEquals("application/xml; charset=UTF-8", writerNoBase.getContentType(req(), rsp)); + assertEquals("application/xml; charset=UTF-8", writerNoBase.getContentType(req(), rsp)); assertEquals("application/xml; charset=UTF-8", writerXmlBase.getContentType(req(), rsp)); assertEquals("application/json; charset=UTF-8", writerJsonBase.getContentType(req(), rsp)); assertEquals("application/octet-stream", writerBinBase.getContentType(req(), rsp)); @@ -153,11 +157,10 @@ public void testStructuredDataViaBaseWriters() throws IOException { writerXmlBase.write(xmlBout, req(), rsp); assertEquals(xml, xmlBout.toString(StandardCharsets.UTF_8)); - // - // assertEquals(xml, writerNoBase.writeToString(req(), rsp)); - // ByteArrayOutputStream noneBout = new ByteArrayOutputStream(); - // writerNoBase.write(noneBout, req(), rsp); - // assertEquals(xml, noneBout.toString(StandardCharsets.UTF_8.toString())); + assertEquals(xml, writerNoBase.writeToString(req(), rsp)); + ByteArrayOutputStream noneBout = new ByteArrayOutputStream(); + writerNoBase.write(noneBout, req(), rsp); + assertEquals(xml, noneBout.toString(StandardCharsets.UTF_8)); // json String json = "{\n" + " \"content\":\"test\",\n" + " \"foo\":\"bar\"}\n"; @@ -204,7 +207,7 @@ private byte[] convertJsonToCborFormat(byte[] inputJson) throws IOException { } /** - * Generates a new {@link RawResponseWriter} wrapping the specified baseWriter name (which much + * Generates a new {@link RawResponseWriter} wrapping the specified baseWriter name (which must * either be an implicitly defined response writer, or one explicitly configured in * solrconfig.xml) *