From 2c8942380c8379507fca11b82a7c83e8b511c828 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 22 Jan 2026 10:16:47 -0500 Subject: [PATCH 01/19] Introduce minimal ADMIN_RESPONSE_WRITERS for admin/container requests Admin/container-level requests (e.g., /admin/cores, /admin/collections) have no associated SolrCore and cannot use the core's response writer registry loaded from ImplicitPlugins.json. This commit introduces ADMIN_RESPONSE_WRITERS, a minimal set of 6 response writers specifically for admin requests. ADMIN_RESPONSE_WRITERS contains only the formats actually needed by admin APIs: - javabin: Required by SolrJ clients (default programmatic API) - json: Required by Admin UI (web interface) - xml: Backward compatibility and occasional test usage - prometheus/openmetrics: Required by /admin/metrics endpoint - standard: Alias for json Benefits: - Clearer separation between admin vs core response writers - Smaller footprint (6 entries vs 14 in DEFAULT_RESPONSE_WRITERS) - Better documentation of admin API requirements - No breaking changes to existing functionality Core-specific requests continue to use the full ImplicitPlugins.json system with ConfigOverlay support. DEFAULT_RESPONSE_WRITERS is marked @Deprecated but remains available for backward compatibility. Changes: - SolrCore: Add ADMIN_RESPONSE_WRITERS map and getAdminResponseWriter() method - SolrCore: Mark DEFAULT_RESPONSE_WRITERS as @Deprecated with updated Javadoc - SolrQueryRequest: Use getAdminResponseWriter() when core is null - HttpSolrCall: Use getAdminResponseWriter() for admin request handling - TestImplicitPlugins: Add tests for admin writers and backward compatibility --- .../admin-response-writers-minimal-set.yml | 22 ++ .../java/org/apache/solr/core/SolrCore.java | 144 ++++++++-- .../apache/solr/request/SolrQueryRequest.java | 19 +- .../org/apache/solr/servlet/HttpSolrCall.java | 3 +- solr/core/src/resources/ImplicitPlugins.json | 41 +++ .../apache/solr/core/TestImplicitPlugins.java | 246 ++++++++++++++++++ 6 files changed, 454 insertions(+), 21 deletions(-) create mode 100644 changelog/unreleased/admin-response-writers-minimal-set.yml create mode 100644 solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java diff --git a/changelog/unreleased/admin-response-writers-minimal-set.yml b/changelog/unreleased/admin-response-writers-minimal-set.yml new file mode 100644 index 000000000000..2f26035ecca0 --- /dev/null +++ b/changelog/unreleased/admin-response-writers-minimal-set.yml @@ -0,0 +1,22 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Introduce minimal ADMIN_RESPONSE_WRITERS for admin/container-level requests. Admin requests now use a minimal set of 6 response writers (javabin, json, xml, prometheus, openmetrics, standard) instead of the full 14-writer DEFAULT_RESPONSE_WRITERS map. Core-specific requests continue to use ImplicitPlugins.json with ConfigOverlay support. +type: changed +authors: + - name: Eric Pugh +links: + - name: GitHub Discussion + url: https://github.com/apache/solr/discussions +notes: | + Admin/container-level requests (e.g., /admin/cores, /admin/collections) have no associated SolrCore + and cannot use the core's response writer registry loaded from ImplicitPlugins.json. Previously, + these requests used DEFAULT_RESPONSE_WRITERS (14 entries). Now they use ADMIN_RESPONSE_WRITERS (6 entries) + containing only the formats actually needed by admin APIs: + - javabin (SolrJ clients) + - json (Admin UI) + - xml (backward compatibility) + - prometheus/openmetrics (metrics endpoint) + - standard (alias for json) + + This provides clearer separation of concerns and better documentation of requirements. Core-specific + requests are unaffected and continue to use the full ImplicitPlugins.json system with ConfigOverlay support. + DEFAULT_RESPONSE_WRITERS remains available but is now deprecated for internal use. \ No newline at end of file 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..bad540a947f8 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -129,8 +129,8 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.CSVResponseWriter; import org.apache.solr.response.CborResponseWriter; +import org.apache.solr.response.CSVResponseWriter; import org.apache.solr.response.GeoJSONResponseWriter; import org.apache.solr.response.GraphMLResponseWriter; import org.apache.solr.response.JacksonJsonWriter; @@ -3088,9 +3088,59 @@ public PluginBag getResponseWriters() { private final PluginBag responseWriters = new PluginBag<>(QueryResponseWriter.class, this); + + /** + * Minimal set of response writers for admin/container-level requests. + * + *

Admin requests have no associated SolrCore and cannot use the core's response writer + * registry loaded from ImplicitPlugins.json. This map provides only the essential formats needed + * by admin APIs: + * + *

    + *
  • javabin - Required by SolrJ clients (the primary programmatic API) + *
  • json - Required by Admin UI and most REST API consumers + *
  • xml - Occasionally used, provides backward compatibility + *
  • prometheus/openmetrics - Required by metrics endpoint + *
+ * + *

Core-specific requests use the full response writer registry loaded from + * ImplicitPlugins.json where ConfigOverlay deletions and customizations are respected. + * + */ + private static final Map ADMIN_RESPONSE_WRITERS; + + /** + * Complete set of default response writers, maintained for backward compatibility. + * + *

Response writers for core-specific requests are now loaded from ImplicitPlugins.json. This + * static map is maintained for: + * + *

    + *
  • External code that may reference this map directly + *
  • Backward compatibility with plugins that expect these to exist + *
+ * + *

Note: Admin/container-level requests should use {@link + * #getAdminResponseWriter(String)} instead, as they only need a minimal subset of formats. + * + * @deprecated Most internal code should use core-specific writers loaded from + * ImplicitPlugins.json, or {@link #getAdminResponseWriter(String)} for admin requests. + */ + @Deprecated public static final Map DEFAULT_RESPONSE_WRITERS; static { + // Minimal set for admin/container requests (no core available) + HashMap adminWriters = new HashMap<>(6, 1); + adminWriters.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); + adminWriters.put(CommonParams.JSON, new JacksonJsonWriter()); + adminWriters.put("standard", adminWriters.get(CommonParams.JSON)); // Alias for JSON + adminWriters.put("xml", new XMLResponseWriter()); + adminWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); + adminWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); + ADMIN_RESPONSE_WRITERS = Collections.unmodifiableMap(adminWriters); + + // Complete set for backward compatibility HashMap m = new HashMap<>(15, 1); m.put("xml", new XMLResponseWriter()); m.put(CommonParams.JSON, new JacksonJsonWriter()); @@ -3148,11 +3198,54 @@ default String getContentType() { } /** - * Configure the query response writers. There will always be a default writer; additional writers - * may also be configured. + * Gets a response writer suitable for admin/container-level requests. Only provides essential + * formats (javabin, json, xml, prometheus, openmetrics). + * + *

This method should be used for admin/container requests that have no associated SolrCore. + * For core-specific requests, use {@link SolrCore#getQueryResponseWriter(String)} which loads + * from ImplicitPlugins.json and respects ConfigOverlay settings. + * + * @param writerName the writer name, or null for default + * @return the response writer, never null (returns "standard"/json if not found) + */ + public static QueryResponseWriter getAdminResponseWriter(String writerName) { + if (writerName == null || writerName.isEmpty()) { + return ADMIN_RESPONSE_WRITERS.get("standard"); + } + return ADMIN_RESPONSE_WRITERS.getOrDefault(writerName, ADMIN_RESPONSE_WRITERS.get("standard")); + } + + /** + * Initializes query response writers. Response writers from {@code ImplicitPlugins.json} may also + * be configured. */ private void initWriters() { - responseWriters.init(DEFAULT_RESPONSE_WRITERS, this); + // Build default writers map from implicit plugins + Map defaultWriters = new HashMap<>(); + + // Load writers from ImplicitPlugins.json + List implicitWriters = getImplicitResponseWriters(); + for (PluginInfo info : implicitWriters) { + try { + QueryResponseWriter writer = + createInstance( + info.className, + QueryResponseWriter.class, + "queryResponseWriter", + null, + getResourceLoader()); + defaultWriters.put(info.name, writer); + } catch (Exception e) { + log.warn("Failed to load implicit response writer: {}", info.name, e); + } + } + + // Add special filestream writer (custom implementation) + defaultWriters.put(ReplicationAPIBase.FILE_STREAM, getFileStreamWriter()); + + // 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"); } @@ -3614,32 +3707,49 @@ public void cleanupOldIndexDirectories(boolean reload) { } } - private static final class ImplicitHolder { - private ImplicitHolder() {} + private static final class ImplicitPluginsHolder { + private ImplicitPluginsHolder() {} - private static final List INSTANCE; + private static final Map> ALL_IMPLICIT_PLUGINS; static { @SuppressWarnings("unchecked") Map implicitPluginsInfo = (Map) Utils.fromJSONResource(SolrCore.class.getClassLoader(), "ImplicitPlugins.json"); - @SuppressWarnings("unchecked") - Map> requestHandlers = - (Map>) implicitPluginsInfo.get(SolrRequestHandler.TYPE); - List implicits = new ArrayList<>(requestHandlers.size()); - for (Map.Entry> entry : requestHandlers.entrySet()) { - Map info = entry.getValue(); - info.put(CommonParams.NAME, entry.getKey()); - implicits.add(new PluginInfo(SolrRequestHandler.TYPE, info)); + Map> plugins = new HashMap<>(); + + // Load all plugin types from the JSON + for (Map.Entry entry : implicitPluginsInfo.entrySet()) { + String pluginType = entry.getKey(); + @SuppressWarnings("unchecked") + Map> pluginConfigs = + (Map>) entry.getValue(); + + List pluginInfos = new ArrayList<>(pluginConfigs.size()); + for (Map.Entry> plugin : pluginConfigs.entrySet()) { + Map info = plugin.getValue(); + info.put(CommonParams.NAME, plugin.getKey()); + pluginInfos.add(new PluginInfo(pluginType, info)); + } + plugins.put(pluginType, Collections.unmodifiableList(pluginInfos)); } - INSTANCE = Collections.unmodifiableList(implicits); + + ALL_IMPLICIT_PLUGINS = Collections.unmodifiableMap(plugins); + } + + public static List getImplicitPlugins(String type) { + return ALL_IMPLICIT_PLUGINS.getOrDefault(type, Collections.emptyList()); } } public List getImplicitHandlers() { - return ImplicitHolder.INSTANCE; + return ImplicitPluginsHolder.getImplicitPlugins(SolrRequestHandler.TYPE); + } + + public List getImplicitResponseWriters() { + return ImplicitPluginsHolder.getImplicitPlugins("queryResponseWriter"); } public CancellableQueryTracker getCancellableQueryTracker() { 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..e05e64dc67c4 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -195,16 +195,29 @@ default CloudDescriptor getCloudDescriptor() { return getCore().getCoreDescriptor().getCloudDescriptor(); } - /** The writer to use for this request, considering {@link CommonParams#WT}. Never null. */ + /** + * The writer to use for this request, considering {@link CommonParams#WT}. Never null. + * + *

If a core is available, uses the core's response writer registry (loaded from + * ImplicitPlugins.json with ConfigOverlay support). If no core is available (e.g., for + * admin/container requests), uses a minimal set of admin-appropriate writers. + */ 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 (core != null) { + // Core-specific request: use full ImplicitPlugins.json registry return core.getQueryResponseWriter(wt); } else { - return SolrCore.DEFAULT_RESPONSE_WRITERS.getOrDefault( - wt, SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard")); + // Admin/container request: use minimal admin writers + // return SolrCore.getAdminResponseWriter(wt); + QueryResponseWriter qw = SolrCore.getAdminResponseWriter(wt); + if (qw == null) { + // not sure this is needed. + qw = SolrCore.getAdminResponseWriter("standard"); + } + return qw; } } 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 db998ff9b8c8..1a8fc9eb842c 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -735,8 +735,9 @@ protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOExce solrResp.getToLogAsString("[admin]")); } } + // Admin requests have no core, use minimal admin-specific writers QueryResponseWriter respWriter = - SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT)); + SolrCore.getAdminResponseWriter(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = getResponseWriter(); writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod())); if (shouldAudit()) { diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json index 4154e70ded9e..dfd3904da09d 100644 --- a/solr/core/src/resources/ImplicitPlugins.json +++ b/solr/core/src/resources/ImplicitPlugins.json @@ -157,5 +157,46 @@ "activetaskslist" ] } + }, + "queryResponseWriter": { + "xml": { + "class": "solr.XMLResponseWriter" + }, + "json": { + "class": "solr.JacksonJsonWriter" + }, + "standard": { + "class": "solr.JacksonJsonWriter" + }, + "geojson": { + "class": "solr.GeoJSONResponseWriter" + }, + "graphml": { + "class": "solr.GraphMLResponseWriter" + }, + "raw": { + "class": "solr.RawResponseWriter" + }, + "javabin": { + "class": "solr.JavaBinResponseWriter" + }, + "cbor": { + "class": "solr.CborResponseWriter" + }, + "csv": { + "class": "solr.CSVResponseWriter" + }, + "schema.xml": { + "class": "solr.SchemaXmlResponseWriter" + }, + "smile": { + "class": "solr.SmileResponseWriter" + }, + "prometheus": { + "class": "solr.PrometheusResponseWriter" + }, + "openmetrics": { + "class": "solr.PrometheusResponseWriter" + } } } diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java new file mode 100644 index 000000000000..246d418a869a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -0,0 +1,246 @@ +/* + * 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.core; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.response.QueryResponseWriter; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests for implicit plugins loaded from ImplicitPlugins.json. + * + *

This test class verifies: + * + *

    + *
  • Request handlers are loaded from ImplicitPlugins.json + *
  • Response writers are loaded from ImplicitPlugins.json for core requests + *
  • Admin response writers use a minimal set (ADMIN_RESPONSE_WRITERS) + *
  • Backward compatibility is maintained (DEFAULT_RESPONSE_WRITERS still exists) + *
+ */ +public class TestImplicitPlugins extends SolrTestCaseJ4 { + + @BeforeClass + public static void beforeClass() throws Exception { + initCore("solrconfig.xml", "schema.xml"); + } + + // ========== Request Handler Tests ========== + + @Test + public void testImplicitRequestHandlers() { + final SolrCore core = h.getCore(); + final List implicitHandlers = core.getImplicitHandlers(); + + assertNotNull("Implicit handlers should not be null", implicitHandlers); + assertFalse("Should have implicit handlers", implicitHandlers.isEmpty()); + + // Verify some key handlers are present + Set handlerNames = new HashSet<>(); + for (PluginInfo handler : implicitHandlers) { + handlerNames.add(handler.name); + } + + assertTrue("Should have /update handler", handlerNames.contains("/update")); + assertTrue("Should have /admin/ping handler", handlerNames.contains("/admin/ping")); + assertTrue("Should have /config handler", handlerNames.contains("/config")); + assertTrue("Should have /schema handler", handlerNames.contains("/schema")); + } + + // ========== Core Response Writer Tests (ImplicitPlugins.json) ========== + + @Test + public void testImplicitResponseWriters() { + final SolrCore core = h.getCore(); + final List implicitWriters = core.getImplicitResponseWriters(); + + assertNotNull("Implicit response writers should not be null", implicitWriters); + assertFalse("Should have implicit response writers", implicitWriters.isEmpty()); + + // Verify key writers are present + Set writerNames = new HashSet<>(); + for (PluginInfo writer : implicitWriters) { + writerNames.add(writer.name); + } + + assertTrue("Should have xml writer", writerNames.contains("xml")); + assertTrue("Should have json writer", writerNames.contains("json")); + assertTrue("Should have csv writer", writerNames.contains("csv")); + assertTrue("Should have javabin writer", writerNames.contains("javabin")); + } + + @Test + public void testImplicitWritersHaveCorrectClass() { + final SolrCore core = h.getCore(); + final List implicitWriters = core.getImplicitResponseWriters(); + + for (PluginInfo writer : implicitWriters) { + assertNotNull("Writer should have a name", writer.name); + assertNotNull("Writer should have a className", writer.className); + assertTrue( + "Writer className should start with 'solr.'", writer.className.startsWith("solr.")); + } + } + + @Test + public void testCoreResponseWritersAreAvailable() { + final SolrCore core = h.getCore(); + + // Verify that writers loaded from ImplicitPlugins.json are actually available for core requests + assertNotNull("Core should have xml writer", core.getQueryResponseWriter("xml")); + assertNotNull("Core should have json writer", core.getQueryResponseWriter("json")); + assertNotNull("Core should have csv writer", core.getQueryResponseWriter("csv")); + assertNotNull("Core should have javabin writer", core.getQueryResponseWriter("javabin")); + assertNotNull("Core should have standard writer", core.getQueryResponseWriter("standard")); + assertNotNull("Core should have geojson writer", core.getQueryResponseWriter("geojson")); + assertNotNull("Core should have graphml writer", core.getQueryResponseWriter("graphml")); + assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); + } + + // ========== Admin Response Writer Tests (ADMIN_RESPONSE_WRITERS) ========== + + @Test + public void testAdminResponseWritersMinimalSet() { + // Admin requests have no SolrCore, so they use ADMIN_RESPONSE_WRITERS + // which contains only the essential formats: javabin, json, xml, prometheus, openmetrics, standard + + // Verify all admin writers exist + assertNotNull( + "Admin javabin writer should exist", SolrCore.getAdminResponseWriter(CommonParams.JAVABIN)); + assertNotNull( + "Admin json writer should exist", SolrCore.getAdminResponseWriter(CommonParams.JSON)); + assertNotNull("Admin xml writer should exist", SolrCore.getAdminResponseWriter("xml")); + assertNotNull( + "Admin prometheus writer should exist", SolrCore.getAdminResponseWriter("prometheus")); + assertNotNull( + "Admin openmetrics writer should exist", SolrCore.getAdminResponseWriter("openmetrics")); + assertNotNull( + "Admin standard writer should exist", SolrCore.getAdminResponseWriter("standard")); + } + + @Test + public void testAdminResponseWritersFallbackBehavior() { + // Test that null/empty defaults to standard + QueryResponseWriter nullWriter = SolrCore.getAdminResponseWriter(null); + QueryResponseWriter emptyWriter = SolrCore.getAdminResponseWriter(""); + QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); + + assertNotNull("Null writer should not be null", nullWriter); + assertNotNull("Empty writer should not be null", emptyWriter); + assertNotNull("Standard writer should not be null", standardWriter); + assertSame("Null writer should return standard writer", standardWriter, nullWriter); + assertSame("Empty writer should return standard writer", standardWriter, emptyWriter); + } + + @Test + public void testAdminResponseWritersUnknownFormat() { + // Test that unknown formats default to standard (json) + QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); + QueryResponseWriter unknownWriter = SolrCore.getAdminResponseWriter("csv"); + + assertNotNull("Unknown writer should not be null", unknownWriter); + assertSame( + "Unknown formats (like csv) should return standard writer for admin requests", + standardWriter, + unknownWriter); + } + + // ========== Core vs Admin Writer Separation Tests ========== + + @Test + public void testCoreAndAdminWritersSeparation() { + final SolrCore core = h.getCore(); + + // Core requests have access to all writers from ImplicitPlugins.json + assertNotNull("Core should have csv writer", core.getQueryResponseWriter("csv")); + assertNotNull("Core should have geojson writer", core.getQueryResponseWriter("geojson")); + assertNotNull("Core should have graphml writer", core.getQueryResponseWriter("graphml")); + assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); + + // Admin requests only have access to minimal set + // (csv, geojson, graphml, smile are NOT in ADMIN_RESPONSE_WRITERS) + QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); + assertSame( + "Admin csv request should fall back to standard", + standardWriter, + SolrCore.getAdminResponseWriter("csv")); + assertSame( + "Admin geojson request should fall back to standard", + standardWriter, + SolrCore.getAdminResponseWriter("geojson")); + assertSame( + "Admin graphml request should fall back to standard", + standardWriter, + SolrCore.getAdminResponseWriter("graphml")); + assertSame( + "Admin smile request should fall back to standard", + standardWriter, + SolrCore.getAdminResponseWriter("smile")); + } + + @Test + public void testCoreAndAdminBothHaveCommonWriters() { + final SolrCore core = h.getCore(); + + // Both core and admin should have these common formats + // (though they may be different instances) + QueryResponseWriter coreJsonWriter = core.getQueryResponseWriter(CommonParams.JSON); + QueryResponseWriter adminJsonWriter = SolrCore.getAdminResponseWriter(CommonParams.JSON); + assertNotNull("Core json writer should not be null", coreJsonWriter); + assertNotNull("Admin json writer should not be null", adminJsonWriter); + + QueryResponseWriter coreXmlWriter = core.getQueryResponseWriter("xml"); + QueryResponseWriter adminXmlWriter = SolrCore.getAdminResponseWriter("xml"); + assertNotNull("Core xml writer should not be null", coreXmlWriter); + assertNotNull("Admin xml writer should not be null", adminXmlWriter); + } + + // ========== Backward Compatibility Tests ========== + + @Test + public void testDefaultResponseWritersBackwardCompatibility() { + // Verify the deprecated DEFAULT_RESPONSE_WRITERS map still exists for external code + assertNotNull( + "DEFAULT_RESPONSE_WRITERS should still exist for backward compatibility", + SolrCore.DEFAULT_RESPONSE_WRITERS); + assertFalse( + "DEFAULT_RESPONSE_WRITERS should not be empty", + SolrCore.DEFAULT_RESPONSE_WRITERS.isEmpty()); + + // Verify it contains all the expected writers (both core and admin formats) + assertTrue( + "Should have json in DEFAULT_RESPONSE_WRITERS", + SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey(CommonParams.JSON)); + assertTrue( + "Should have xml in DEFAULT_RESPONSE_WRITERS", + SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("xml")); + assertTrue( + "Should have csv in DEFAULT_RESPONSE_WRITERS", + SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("csv")); + assertTrue( + "Should have geojson in DEFAULT_RESPONSE_WRITERS", + SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("geojson")); + assertTrue( + "Should have javabin in DEFAULT_RESPONSE_WRITERS", + SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey(CommonParams.JAVABIN)); + } +} \ No newline at end of file From ec3b79e00a27660e840394c31b057d9c9e188927 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 22 Jan 2026 14:00:55 -0500 Subject: [PATCH 02/19] Remove the DEFAULT_RESPONSE_WRITERS --- .../java/org/apache/solr/core/SolrCore.java | 46 ------------------- .../apache/solr/core/TestImplicitPlugins.java | 35 ++------------ 2 files changed, 3 insertions(+), 78 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 bad540a947f8..5b7674b57a08 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -129,17 +129,10 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.CborResponseWriter; -import org.apache.solr.response.CSVResponseWriter; -import org.apache.solr.response.GeoJSONResponseWriter; -import org.apache.solr.response.GraphMLResponseWriter; import org.apache.solr.response.JacksonJsonWriter; import org.apache.solr.response.JavaBinResponseWriter; import org.apache.solr.response.PrometheusResponseWriter; import org.apache.solr.response.QueryResponseWriter; -import org.apache.solr.response.RawResponseWriter; -import org.apache.solr.response.SchemaXmlResponseWriter; -import org.apache.solr.response.SmileResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.response.XMLResponseWriter; import org.apache.solr.response.transform.TransformerFactory; @@ -3105,30 +3098,9 @@ public PluginBag getResponseWriters() { * *

Core-specific requests use the full response writer registry loaded from * ImplicitPlugins.json where ConfigOverlay deletions and customizations are respected. - * */ private static final Map ADMIN_RESPONSE_WRITERS; - /** - * Complete set of default response writers, maintained for backward compatibility. - * - *

Response writers for core-specific requests are now loaded from ImplicitPlugins.json. This - * static map is maintained for: - * - *

    - *
  • External code that may reference this map directly - *
  • Backward compatibility with plugins that expect these to exist - *
- * - *

Note: Admin/container-level requests should use {@link - * #getAdminResponseWriter(String)} instead, as they only need a minimal subset of formats. - * - * @deprecated Most internal code should use core-specific writers loaded from - * ImplicitPlugins.json, or {@link #getAdminResponseWriter(String)} for admin requests. - */ - @Deprecated - public static final Map DEFAULT_RESPONSE_WRITERS; - static { // Minimal set for admin/container requests (no core available) HashMap adminWriters = new HashMap<>(6, 1); @@ -3139,24 +3111,6 @@ public PluginBag getResponseWriters() { adminWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); adminWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); ADMIN_RESPONSE_WRITERS = Collections.unmodifiableMap(adminWriters); - - // Complete set for backward compatibility - 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()); - m.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); - m.put("cbor", new CborResponseWriter()); - m.put("csv", new CSVResponseWriter()); - m.put("schema.xml", new SchemaXmlResponseWriter()); - m.put("smile", new SmileResponseWriter()); - m.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); - m.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); - m.put(ReplicationAPIBase.FILE_STREAM, getFileStreamWriter()); - DEFAULT_RESPONSE_WRITERS = Collections.unmodifiableMap(m); } private static JavaBinResponseWriter getFileStreamWriter() { diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java index 246d418a869a..f48793dc41da 100644 --- a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -121,7 +121,8 @@ public void testCoreResponseWritersAreAvailable() { @Test public void testAdminResponseWritersMinimalSet() { // Admin requests have no SolrCore, so they use ADMIN_RESPONSE_WRITERS - // which contains only the essential formats: javabin, json, xml, prometheus, openmetrics, standard + // which contains only the essential formats: javabin, json, xml, prometheus, openmetrics, + // standard // Verify all admin writers exist assertNotNull( @@ -213,34 +214,4 @@ public void testCoreAndAdminBothHaveCommonWriters() { assertNotNull("Core xml writer should not be null", coreXmlWriter); assertNotNull("Admin xml writer should not be null", adminXmlWriter); } - - // ========== Backward Compatibility Tests ========== - - @Test - public void testDefaultResponseWritersBackwardCompatibility() { - // Verify the deprecated DEFAULT_RESPONSE_WRITERS map still exists for external code - assertNotNull( - "DEFAULT_RESPONSE_WRITERS should still exist for backward compatibility", - SolrCore.DEFAULT_RESPONSE_WRITERS); - assertFalse( - "DEFAULT_RESPONSE_WRITERS should not be empty", - SolrCore.DEFAULT_RESPONSE_WRITERS.isEmpty()); - - // Verify it contains all the expected writers (both core and admin formats) - assertTrue( - "Should have json in DEFAULT_RESPONSE_WRITERS", - SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey(CommonParams.JSON)); - assertTrue( - "Should have xml in DEFAULT_RESPONSE_WRITERS", - SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("xml")); - assertTrue( - "Should have csv in DEFAULT_RESPONSE_WRITERS", - SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("csv")); - assertTrue( - "Should have geojson in DEFAULT_RESPONSE_WRITERS", - SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey("geojson")); - assertTrue( - "Should have javabin in DEFAULT_RESPONSE_WRITERS", - SolrCore.DEFAULT_RESPONSE_WRITERS.containsKey(CommonParams.JAVABIN)); - } -} \ No newline at end of file +} From 509e490ef7b57bab2235ae06c8b05a1d2dfbd3f7 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 23 Jan 2026 17:51:52 -0500 Subject: [PATCH 03/19] Introduce BuiltInResponseWriterRegistery that has static set of BUILTIN_WRITERS --- .../bench/search/QueryResponseWriters.java | 2 - .../java/org/apache/solr/core/SolrCore.java | 53 +----- .../apache/solr/request/SolrQueryRequest.java | 14 +- .../BuiltInResponseWriterRegistry.java | 115 ++++++++++++ .../org/apache/solr/servlet/HttpSolrCall.java | 5 +- .../apache/solr/core/TestImplicitPlugins.java | 173 ++---------------- .../TestBuiltInResponseWriterRegistry.java | 73 ++++++++ 7 files changed, 219 insertions(+), 216 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java create mode 100644 solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java diff --git a/solr/benchmark/src/java/org/apache/solr/bench/search/QueryResponseWriters.java b/solr/benchmark/src/java/org/apache/solr/bench/search/QueryResponseWriters.java index 67d931c9217a..4b6118fed27c 100644 --- a/solr/benchmark/src/java/org/apache/solr/bench/search/QueryResponseWriters.java +++ b/solr/benchmark/src/java/org/apache/solr/bench/search/QueryResponseWriters.java @@ -30,7 +30,6 @@ import org.apache.solr.client.solrj.response.InputStreamResponseParser; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; -import org.apache.solr.core.SolrCore; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -58,7 +57,6 @@ public class QueryResponseWriters { @State(Scope.Benchmark) public static class BenchState { - /** See {@link SolrCore#DEFAULT_RESPONSE_WRITERS} */ @Param({CommonParams.JAVABIN, CommonParams.JSON, "cbor", "smile", "xml", "raw"}) String wt; 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 5b7674b57a08..2416b895f8d8 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -17,8 +17,6 @@ package org.apache.solr.core; import static org.apache.solr.common.params.CommonParams.PATH; -import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT; -import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT; import static org.apache.solr.metrics.SolrCoreMetricManager.COLLECTION_ATTR; import static org.apache.solr.metrics.SolrCoreMetricManager.CORE_ATTR; import static org.apache.solr.metrics.SolrCoreMetricManager.REPLICA_TYPE_ATTR; @@ -129,12 +127,9 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.JacksonJsonWriter; import org.apache.solr.response.JavaBinResponseWriter; -import org.apache.solr.response.PrometheusResponseWriter; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; -import org.apache.solr.response.XMLResponseWriter; import org.apache.solr.response.transform.TransformerFactory; import org.apache.solr.rest.ManagedResourceStorage; import org.apache.solr.rest.ManagedResourceStorage.StorageIO; @@ -3082,37 +3077,6 @@ public PluginBag getResponseWriters() { private final PluginBag responseWriters = new PluginBag<>(QueryResponseWriter.class, this); - /** - * Minimal set of response writers for admin/container-level requests. - * - *

Admin requests have no associated SolrCore and cannot use the core's response writer - * registry loaded from ImplicitPlugins.json. This map provides only the essential formats needed - * by admin APIs: - * - *

    - *
  • javabin - Required by SolrJ clients (the primary programmatic API) - *
  • json - Required by Admin UI and most REST API consumers - *
  • xml - Occasionally used, provides backward compatibility - *
  • prometheus/openmetrics - Required by metrics endpoint - *
- * - *

Core-specific requests use the full response writer registry loaded from - * ImplicitPlugins.json where ConfigOverlay deletions and customizations are respected. - */ - private static final Map ADMIN_RESPONSE_WRITERS; - - static { - // Minimal set for admin/container requests (no core available) - HashMap adminWriters = new HashMap<>(6, 1); - adminWriters.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); - adminWriters.put(CommonParams.JSON, new JacksonJsonWriter()); - adminWriters.put("standard", adminWriters.get(CommonParams.JSON)); // Alias for JSON - adminWriters.put("xml", new XMLResponseWriter()); - adminWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); - adminWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); - ADMIN_RESPONSE_WRITERS = Collections.unmodifiableMap(adminWriters); - } - private static JavaBinResponseWriter getFileStreamWriter() { return new JavaBinResponseWriter() { @Override @@ -3152,21 +3116,16 @@ default String getContentType() { } /** - * Gets a response writer suitable for admin/container-level requests. Only provides essential - * formats (javabin, json, xml, prometheus, openmetrics). - * - *

This method should be used for admin/container requests that have no associated SolrCore. - * For core-specific requests, use {@link SolrCore#getQueryResponseWriter(String)} which loads - * from ImplicitPlugins.json and respects ConfigOverlay settings. + * Gets a response writer suitable for admin/container-level requests. * * @param writerName the writer name, or null for default - * @return the response writer, never null (returns "standard"/json if not found) + * @return the response writer, never null + * @deprecated Use {@link + * org.apache.solr.response.BuiltInResponseWriterRegistry#getWriter(String)} instead. */ + @Deprecated public static QueryResponseWriter getAdminResponseWriter(String writerName) { - if (writerName == null || writerName.isEmpty()) { - return ADMIN_RESPONSE_WRITERS.get("standard"); - } - return ADMIN_RESPONSE_WRITERS.getOrDefault(writerName, ADMIN_RESPONSE_WRITERS.get("standard")); + return org.apache.solr.response.BuiltInResponseWriterRegistry.getWriter(writerName); } /** 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 e05e64dc67c4..247d89592f9d 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -30,6 +30,7 @@ import org.apache.solr.common.util.EnvUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.SolrCore; +import org.apache.solr.response.BuiltInResponseWriterRegistry; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.schema.IndexSchema; import org.apache.solr.search.SolrIndexSearcher; @@ -117,7 +118,7 @@ static boolean disallowPartialResults(SolrParams params) { /** The index searcher associated with this request */ SolrIndexSearcher getSearcher(); - /** The solr core (coordinator, etc) associated with this request */ + /** The solr core (coordinator, etc.) associated with this request */ SolrCore getCore(); /** The schema snapshot from core.getLatestSchema() at request creation. */ @@ -206,18 +207,11 @@ 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) { - // Core-specific request: use full ImplicitPlugins.json registry return core.getQueryResponseWriter(wt); } else { - // Admin/container request: use minimal admin writers - // return SolrCore.getAdminResponseWriter(wt); - QueryResponseWriter qw = SolrCore.getAdminResponseWriter(wt); - if (qw == null) { - // not sure this is needed. - qw = SolrCore.getAdminResponseWriter("standard"); - } - return qw; + return BuiltInResponseWriterRegistry.getWriter(wt); } } diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java new file mode 100644 index 000000000000..e944e3fcde31 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java @@ -0,0 +1,115 @@ +/* + * 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 static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT; +import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.common.params.CommonParams; + +/** + * Registry for built-in response writers in Solr. + * + *

Manages a minimal set of essential response writers that are always available, regardless of + * core configuration or ImplicitPlugins.json settings. These writers are primarily used by + * admin/container-level requests that have no associated SolrCore. + * + *

Built-in writers include essential formats needed by admin APIs and core functionality: + * javabin, json, xml, prometheus, and openmetrics. + * + *

For core-specific requests that need access to the full set of response writers (including + * csv, geojson, graphml, smile, etc.), use the SolrCore's response writer registry which is loaded + * from ImplicitPlugins.json and supports ConfigOverlay customizations. + * + * @since 11.0 + */ +public class BuiltInResponseWriterRegistry { + + /** + * Built-in response writers that are always available. + * + *

Contains only essential formats needed by admin APIs and core functionality: + * + *

    + *
  • javabin - Binary format, efficient for SolrJ clients + *
  • json/standard - JSON format, default for most requests + *
  • xml - XML format, provides backward compatibility + *
  • prometheus/openmetrics - Required by metrics endpoints + *
+ */ + private static final Map BUILTIN_WRITERS; + + static { + // Initialize built-in writers that are always available + Map builtinWriters = new HashMap<>(6, 1); + builtinWriters.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); + builtinWriters.put(CommonParams.JSON, new JacksonJsonWriter()); + builtinWriters.put("standard", builtinWriters.get(CommonParams.JSON)); // Alias for JSON + builtinWriters.put("xml", new XMLResponseWriter()); + builtinWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); + builtinWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); + BUILTIN_WRITERS = Collections.unmodifiableMap(builtinWriters); + } + + /** + * Gets a built-in response writer. + * + *

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. + * + * @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) + */ + public static QueryResponseWriter getWriter(String writerName) { + // carrying over this "standard" thing from original code, but do we want this? null/blank + // means standard? + // feels like null or blank should throw an exception. And a method should be added that is + // getWriter() that + // returns the json guy. I hate passing around nulls and ""... + if (writerName == null || writerName.isEmpty()) { + return BUILTIN_WRITERS.get("standard"); + } + return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard")); + } + + /** + * Returns the set of built-in writer names. + * + *

This is useful for debugging, testing, and API documentation. The returned set is immutable. + * + * @return unmodifiable set of built-in writer names + */ + static java.util.Set getWriterNames() { + return BUILTIN_WRITERS.keySet(); + } + + /** + * Checks if a writer is available as a built-in writer. + * + * @param writerName the writer name to check + * @return true if the writer is available as a built-in writer + */ + static boolean hasWriter(String writerName) { + 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 1a8fc9eb842c..192e922f3baf 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -85,6 +85,7 @@ import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; +import org.apache.solr.response.BuiltInResponseWriterRegistry; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuditEvent; @@ -735,9 +736,9 @@ protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOExce solrResp.getToLogAsString("[admin]")); } } - // Admin requests have no core, use minimal admin-specific writers + // Admin requests have no core, use built-in writers QueryResponseWriter respWriter = - SolrCore.getAdminResponseWriter(solrReq.getParams().get(CommonParams.WT)); + BuiltInResponseWriterRegistry.getWriter(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = getResponseWriter(); writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod())); if (shouldAudit()) { diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java index f48793dc41da..22fa92998667 100644 --- a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -16,11 +16,9 @@ */ package org.apache.solr.core; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.CommonParams; +import org.apache.solr.response.BuiltInResponseWriterRegistry; import org.apache.solr.response.QueryResponseWriter; import org.junit.BeforeClass; import org.junit.Test; @@ -33,8 +31,8 @@ *

    *
  • Request handlers are loaded from ImplicitPlugins.json *
  • Response writers are loaded from ImplicitPlugins.json for core requests - *
  • Admin response writers use a minimal set (ADMIN_RESPONSE_WRITERS) - *
  • Backward compatibility is maintained (DEFAULT_RESPONSE_WRITERS still exists) + *
  • Built in response writers use a minimal set defined in {@link + * org.apache.solr.response.BuiltInResponseWriterRegistry}. *
*/ public class TestImplicitPlugins extends SolrTestCaseJ4 { @@ -44,174 +42,39 @@ public static void beforeClass() throws Exception { initCore("solrconfig.xml", "schema.xml"); } - // ========== Request Handler Tests ========== + // ========== Core vs Built-in Writer Separation Tests ========== @Test - public void testImplicitRequestHandlers() { + public void testCoreAndBuiltInWriterIntegration() { final SolrCore core = h.getCore(); - final List implicitHandlers = core.getImplicitHandlers(); - assertNotNull("Implicit handlers should not be null", implicitHandlers); - assertFalse("Should have implicit handlers", implicitHandlers.isEmpty()); - - // Verify some key handlers are present - Set handlerNames = new HashSet<>(); - for (PluginInfo handler : implicitHandlers) { - handlerNames.add(handler.name); - } - - assertTrue("Should have /update handler", handlerNames.contains("/update")); - assertTrue("Should have /admin/ping handler", handlerNames.contains("/admin/ping")); - assertTrue("Should have /config handler", handlerNames.contains("/config")); - assertTrue("Should have /schema handler", handlerNames.contains("/schema")); - } - - // ========== Core Response Writer Tests (ImplicitPlugins.json) ========== - - @Test - public void testImplicitResponseWriters() { - final SolrCore core = h.getCore(); - final List implicitWriters = core.getImplicitResponseWriters(); - - assertNotNull("Implicit response writers should not be null", implicitWriters); - assertFalse("Should have implicit response writers", implicitWriters.isEmpty()); - - // Verify key writers are present - Set writerNames = new HashSet<>(); - for (PluginInfo writer : implicitWriters) { - writerNames.add(writer.name); - } - - assertTrue("Should have xml writer", writerNames.contains("xml")); - assertTrue("Should have json writer", writerNames.contains("json")); - assertTrue("Should have csv writer", writerNames.contains("csv")); - assertTrue("Should have javabin writer", writerNames.contains("javabin")); - } - - @Test - public void testImplicitWritersHaveCorrectClass() { - final SolrCore core = h.getCore(); - final List implicitWriters = core.getImplicitResponseWriters(); - - for (PluginInfo writer : implicitWriters) { - assertNotNull("Writer should have a name", writer.name); - assertNotNull("Writer should have a className", writer.className); - assertTrue( - "Writer className should start with 'solr.'", writer.className.startsWith("solr.")); - } - } - - @Test - public void testCoreResponseWritersAreAvailable() { - final SolrCore core = h.getCore(); - - // Verify that writers loaded from ImplicitPlugins.json are actually available for core requests - assertNotNull("Core should have xml writer", core.getQueryResponseWriter("xml")); - assertNotNull("Core should have json writer", core.getQueryResponseWriter("json")); + // Test that core has extended writers from ImplicitPlugins.json assertNotNull("Core should have csv writer", core.getQueryResponseWriter("csv")); - assertNotNull("Core should have javabin writer", core.getQueryResponseWriter("javabin")); - assertNotNull("Core should have standard writer", core.getQueryResponseWriter("standard")); assertNotNull("Core should have geojson writer", core.getQueryResponseWriter("geojson")); assertNotNull("Core should have graphml writer", core.getQueryResponseWriter("graphml")); assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); - } - // ========== Admin Response Writer Tests (ADMIN_RESPONSE_WRITERS) ========== - - @Test - public void testAdminResponseWritersMinimalSet() { - // Admin requests have no SolrCore, so they use ADMIN_RESPONSE_WRITERS - // which contains only the essential formats: javabin, json, xml, prometheus, openmetrics, - // standard - - // Verify all admin writers exist - assertNotNull( - "Admin javabin writer should exist", SolrCore.getAdminResponseWriter(CommonParams.JAVABIN)); - assertNotNull( - "Admin json writer should exist", SolrCore.getAdminResponseWriter(CommonParams.JSON)); - assertNotNull("Admin xml writer should exist", SolrCore.getAdminResponseWriter("xml")); - assertNotNull( - "Admin prometheus writer should exist", SolrCore.getAdminResponseWriter("prometheus")); - assertNotNull( - "Admin openmetrics writer should exist", SolrCore.getAdminResponseWriter("openmetrics")); - assertNotNull( - "Admin standard writer should exist", SolrCore.getAdminResponseWriter("standard")); - } - - @Test - public void testAdminResponseWritersFallbackBehavior() { - // Test that null/empty defaults to standard - QueryResponseWriter nullWriter = SolrCore.getAdminResponseWriter(null); - QueryResponseWriter emptyWriter = SolrCore.getAdminResponseWriter(""); - QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); - - assertNotNull("Null writer should not be null", nullWriter); - assertNotNull("Empty writer should not be null", emptyWriter); - assertNotNull("Standard writer should not be null", standardWriter); - assertSame("Null writer should return standard writer", standardWriter, nullWriter); - assertSame("Empty writer should return standard writer", standardWriter, emptyWriter); - } - - @Test - public void testAdminResponseWritersUnknownFormat() { - // Test that unknown formats default to standard (json) - QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); - QueryResponseWriter unknownWriter = SolrCore.getAdminResponseWriter("csv"); - - assertNotNull("Unknown writer should not be null", unknownWriter); - assertSame( - "Unknown formats (like csv) should return standard writer for admin requests", - standardWriter, - unknownWriter); - } - - // ========== Core vs Admin Writer Separation Tests ========== - - @Test - public void testCoreAndAdminWritersSeparation() { - final SolrCore core = h.getCore(); - - // Core requests have access to all writers from ImplicitPlugins.json - assertNotNull("Core should have csv writer", core.getQueryResponseWriter("csv")); - assertNotNull("Core should have geojson writer", core.getQueryResponseWriter("geojson")); - assertNotNull("Core should have graphml writer", core.getQueryResponseWriter("graphml")); - assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); - - // Admin requests only have access to minimal set - // (csv, geojson, graphml, smile are NOT in ADMIN_RESPONSE_WRITERS) - QueryResponseWriter standardWriter = SolrCore.getAdminResponseWriter("standard"); - assertSame( - "Admin csv request should fall back to standard", - standardWriter, - SolrCore.getAdminResponseWriter("csv")); + // Test that built-in registry has minimal set and falls back for extended formats + QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); assertSame( - "Admin geojson request should fall back to standard", + "Built-in csv request should fall back to standard", standardWriter, - SolrCore.getAdminResponseWriter("geojson")); + BuiltInResponseWriterRegistry.getWriter("csv")); assertSame( - "Admin graphml request should fall back to standard", + "Built-in geojson request should fall back to standard", standardWriter, - SolrCore.getAdminResponseWriter("graphml")); - assertSame( - "Admin smile request should fall back to standard", - standardWriter, - SolrCore.getAdminResponseWriter("smile")); - } - - @Test - public void testCoreAndAdminBothHaveCommonWriters() { - final SolrCore core = h.getCore(); + BuiltInResponseWriterRegistry.getWriter("geojson")); - // Both core and admin should have these common formats - // (though they may be different instances) + // Test that both systems have common essential formats (though may be different instances) QueryResponseWriter coreJsonWriter = core.getQueryResponseWriter(CommonParams.JSON); - QueryResponseWriter adminJsonWriter = SolrCore.getAdminResponseWriter(CommonParams.JSON); + QueryResponseWriter builtInJsonWriter = + BuiltInResponseWriterRegistry.getWriter(CommonParams.JSON); assertNotNull("Core json writer should not be null", coreJsonWriter); - assertNotNull("Admin json writer should not be null", adminJsonWriter); + assertNotNull("Built-in json writer should not be null", builtInJsonWriter); QueryResponseWriter coreXmlWriter = core.getQueryResponseWriter("xml"); - QueryResponseWriter adminXmlWriter = SolrCore.getAdminResponseWriter("xml"); + QueryResponseWriter builtInXmlWriter = BuiltInResponseWriterRegistry.getWriter("xml"); assertNotNull("Core xml writer should not be null", coreXmlWriter); - assertNotNull("Admin xml writer should not be null", adminXmlWriter); + assertNotNull("Built-in xml writer should not be null", builtInXmlWriter); } } diff --git a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java new file mode 100644 index 000000000000..e64c37fe636b --- /dev/null +++ b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java @@ -0,0 +1,73 @@ +/* + * 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 static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.apache.solr.SolrTestCaseJ4; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test for {@link BuiltInResponseWriterRegistry}. + * + *

This test validates the registry's behavior for built-in response writers, including + * availability, fallback behavior, and proper format handling. + */ +public class TestBuiltInResponseWriterRegistry extends SolrTestCaseJ4 { + + @BeforeClass + public static void beforeClass() throws Exception { + initCore("solrconfig.xml", "schema.xml"); + } + + @Test + public void testBuiltInWriterFallbackBehavior() { + QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); + + // Test null fallback + QueryResponseWriter nullWriter = BuiltInResponseWriterRegistry.getWriter(null); + assertThat("null writer should not be null", nullWriter, is(not(nullValue()))); + assertThat("null writer should be same as standard", nullWriter, is(standardWriter)); + + // Test empty string fallback + QueryResponseWriter emptyWriter = BuiltInResponseWriterRegistry.getWriter(""); + assertThat("empty writer should not be null", emptyWriter, is(not(nullValue()))); + assertThat("empty writer should be same as standard", emptyWriter, is(standardWriter)); + + // Test unknown format fallback + QueryResponseWriter unknownWriter = BuiltInResponseWriterRegistry.getWriter("nonexistent"); + assertThat("unknown writer should not be null", unknownWriter, is(not(nullValue()))); + assertThat("unknown writer should be same as standard", unknownWriter, is(standardWriter)); + } + + @Test + public void testBuiltInWriterLimitedSet() { + QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); + + // 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! + assertThat( + "geojson should fall back to standard", + BuiltInResponseWriterRegistry.getWriter("geojson"), + is(standardWriter)); + } +} From 9735afc46f7c64ee433a757d5f6b76456b5f6386 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 24 Jan 2026 07:57:39 -0500 Subject: [PATCH 04/19] reviewing code for dead methods etc --- .../BuiltInResponseWriterRegistry.java | 23 ------------------- .../TestBuiltInResponseWriterRegistry.java | 15 +++--------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java index e944e3fcde31..7e0a67258d3d 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java @@ -37,8 +37,6 @@ *

For core-specific requests that need access to the full set of response writers (including * csv, geojson, graphml, smile, etc.), use the SolrCore's response writer registry which is loaded * from ImplicitPlugins.json and supports ConfigOverlay customizations. - * - * @since 11.0 */ public class BuiltInResponseWriterRegistry { @@ -91,25 +89,4 @@ public static QueryResponseWriter getWriter(String writerName) { } return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard")); } - - /** - * Returns the set of built-in writer names. - * - *

This is useful for debugging, testing, and API documentation. The returned set is immutable. - * - * @return unmodifiable set of built-in writer names - */ - static java.util.Set getWriterNames() { - return BUILTIN_WRITERS.keySet(); - } - - /** - * Checks if a writer is available as a built-in writer. - * - * @param writerName the writer name to check - * @return true if the writer is available as a built-in writer - */ - static boolean hasWriter(String writerName) { - return BUILTIN_WRITERS.containsKey(writerName); - } } diff --git a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java index e64c37fe636b..65e75b984d0c 100644 --- a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java +++ b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java @@ -19,25 +19,16 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; import org.apache.solr.SolrTestCaseJ4; -import org.junit.BeforeClass; import org.junit.Test; /** - * Test for {@link BuiltInResponseWriterRegistry}. - * - *

This test validates the registry's behavior for built-in response writers, including - * availability, fallback behavior, and proper format handling. + * This test validates the registry's behavior for built-in response writers, including + * availability, fallback behavior, and proper format handling. Notice there is no core configured! */ public class TestBuiltInResponseWriterRegistry extends SolrTestCaseJ4 { - @BeforeClass - public static void beforeClass() throws Exception { - initCore("solrconfig.xml", "schema.xml"); - } - @Test public void testBuiltInWriterFallbackBehavior() { QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); @@ -64,7 +55,7 @@ public void testBuiltInWriterLimitedSet() { // 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! + // I think this standard thing is weird... I think it should throw an exception! assertThat( "geojson should fall back to standard", BuiltInResponseWriterRegistry.getWriter("geojson"), From ac54910a006a04f1423817294ca102acda2e7365 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 24 Jan 2026 08:03:51 -0500 Subject: [PATCH 05/19] "Registry" isn't a suffix we use, and be more consistent. The **`BuiltInResponseWriters`** name would be the most consistent with existing Solr patterns, especially since `RequestHandlers` serves a very similar purpose for request handlers that your class serves for response writers. --- .../src/java/org/apache/solr/core/SolrCore.java | 6 +++--- .../apache/solr/request/SolrQueryRequest.java | 4 ++-- ...Registry.java => BuiltInResponseWriters.java} | 2 +- .../org/apache/solr/servlet/HttpSolrCall.java | 4 ++-- .../apache/solr/core/TestImplicitPlugins.java | 16 +++++++--------- ...stry.java => TestBuiltInResponseWriters.java} | 14 +++++++------- 6 files changed, 22 insertions(+), 24 deletions(-) rename solr/core/src/java/org/apache/solr/response/{BuiltInResponseWriterRegistry.java => BuiltInResponseWriters.java} (98%) rename solr/core/src/test/org/apache/solr/response/{TestBuiltInResponseWriterRegistry.java => TestBuiltInResponseWriters.java} (79%) 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 2416b895f8d8..84fb501c187c 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -127,6 +127,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; +import org.apache.solr.response.BuiltInResponseWriters; import org.apache.solr.response.JavaBinResponseWriter; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; @@ -3120,12 +3121,11 @@ default String getContentType() { * * @param writerName the writer name, or null for default * @return the response writer, never null - * @deprecated Use {@link - * org.apache.solr.response.BuiltInResponseWriterRegistry#getWriter(String)} instead. + * @deprecated Use {@link BuiltInResponseWriters#getWriter(String)} instead. */ @Deprecated public static QueryResponseWriter getAdminResponseWriter(String writerName) { - return org.apache.solr.response.BuiltInResponseWriterRegistry.getWriter(writerName); + return BuiltInResponseWriters.getWriter(writerName); } /** 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 247d89592f9d..9cb993098e34 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -30,7 +30,7 @@ import org.apache.solr.common.util.EnvUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.SolrCore; -import org.apache.solr.response.BuiltInResponseWriterRegistry; +import org.apache.solr.response.BuiltInResponseWriters; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.schema.IndexSchema; import org.apache.solr.search.SolrIndexSearcher; @@ -211,7 +211,7 @@ default QueryResponseWriter getResponseWriter() { if (core != null) { return core.getQueryResponseWriter(wt); } else { - return BuiltInResponseWriterRegistry.getWriter(wt); + return BuiltInResponseWriters.getWriter(wt); } } diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java similarity index 98% rename from solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java rename to solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index 7e0a67258d3d..99b1b57b06a7 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriterRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -38,7 +38,7 @@ * csv, geojson, graphml, smile, etc.), use the SolrCore's response writer registry which is loaded * from ImplicitPlugins.json and supports ConfigOverlay customizations. */ -public class BuiltInResponseWriterRegistry { +public class BuiltInResponseWriters { /** * Built-in response writers that are always available. 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 192e922f3baf..634845e0d282 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -85,7 +85,7 @@ import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.BuiltInResponseWriterRegistry; +import org.apache.solr.response.BuiltInResponseWriters; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuditEvent; @@ -738,7 +738,7 @@ protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOExce } // Admin requests have no core, use built-in writers QueryResponseWriter respWriter = - BuiltInResponseWriterRegistry.getWriter(solrReq.getParams().get(CommonParams.WT)); + BuiltInResponseWriters.getWriter(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = getResponseWriter(); writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod())); if (shouldAudit()) { diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java index 22fa92998667..6158d811fdad 100644 --- a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -18,7 +18,7 @@ import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.CommonParams; -import org.apache.solr.response.BuiltInResponseWriterRegistry; +import org.apache.solr.response.BuiltInResponseWriters; import org.apache.solr.response.QueryResponseWriter; import org.junit.BeforeClass; import org.junit.Test; @@ -31,8 +31,7 @@ *

    *
  • Request handlers are loaded from ImplicitPlugins.json *
  • Response writers are loaded from ImplicitPlugins.json for core requests - *
  • Built in response writers use a minimal set defined in {@link - * org.apache.solr.response.BuiltInResponseWriterRegistry}. + *
  • Built in response writers use a minimal set defined in {@link BuiltInResponseWriters}. *
*/ public class TestImplicitPlugins extends SolrTestCaseJ4 { @@ -55,25 +54,24 @@ public void testCoreAndBuiltInWriterIntegration() { assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); // Test that built-in registry has minimal set and falls back for extended formats - QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); + QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); assertSame( "Built-in csv request should fall back to standard", standardWriter, - BuiltInResponseWriterRegistry.getWriter("csv")); + BuiltInResponseWriters.getWriter("csv")); assertSame( "Built-in geojson request should fall back to standard", standardWriter, - BuiltInResponseWriterRegistry.getWriter("geojson")); + BuiltInResponseWriters.getWriter("geojson")); // Test that both systems have common essential formats (though may be different instances) QueryResponseWriter coreJsonWriter = core.getQueryResponseWriter(CommonParams.JSON); - QueryResponseWriter builtInJsonWriter = - BuiltInResponseWriterRegistry.getWriter(CommonParams.JSON); + QueryResponseWriter builtInJsonWriter = BuiltInResponseWriters.getWriter(CommonParams.JSON); assertNotNull("Core json writer should not be null", coreJsonWriter); assertNotNull("Built-in json writer should not be null", builtInJsonWriter); QueryResponseWriter coreXmlWriter = core.getQueryResponseWriter("xml"); - QueryResponseWriter builtInXmlWriter = BuiltInResponseWriterRegistry.getWriter("xml"); + QueryResponseWriter builtInXmlWriter = BuiltInResponseWriters.getWriter("xml"); assertNotNull("Core xml writer should not be null", coreXmlWriter); assertNotNull("Built-in xml writer should not be null", builtInXmlWriter); } diff --git a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java similarity index 79% rename from solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java rename to solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java index 65e75b984d0c..60115a89be36 100644 --- a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriterRegistry.java +++ b/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java @@ -27,38 +27,38 @@ * This test validates the registry's behavior for built-in response writers, including * availability, fallback behavior, and proper format handling. Notice there is no core configured! */ -public class TestBuiltInResponseWriterRegistry extends SolrTestCaseJ4 { +public class TestBuiltInResponseWriters extends SolrTestCaseJ4 { @Test public void testBuiltInWriterFallbackBehavior() { - QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); + QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); // Test null fallback - QueryResponseWriter nullWriter = BuiltInResponseWriterRegistry.getWriter(null); + QueryResponseWriter nullWriter = BuiltInResponseWriters.getWriter(null); assertThat("null writer should not be null", nullWriter, is(not(nullValue()))); assertThat("null writer should be same as standard", nullWriter, is(standardWriter)); // Test empty string fallback - QueryResponseWriter emptyWriter = BuiltInResponseWriterRegistry.getWriter(""); + QueryResponseWriter emptyWriter = BuiltInResponseWriters.getWriter(""); assertThat("empty writer should not be null", emptyWriter, is(not(nullValue()))); assertThat("empty writer should be same as standard", emptyWriter, is(standardWriter)); // Test unknown format fallback - QueryResponseWriter unknownWriter = BuiltInResponseWriterRegistry.getWriter("nonexistent"); + QueryResponseWriter unknownWriter = BuiltInResponseWriters.getWriter("nonexistent"); assertThat("unknown writer should not be null", unknownWriter, is(not(nullValue()))); assertThat("unknown writer should be same as standard", unknownWriter, is(standardWriter)); } @Test public void testBuiltInWriterLimitedSet() { - QueryResponseWriter standardWriter = BuiltInResponseWriterRegistry.getWriter("standard"); + QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); // 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! assertThat( "geojson should fall back to standard", - BuiltInResponseWriterRegistry.getWriter("geojson"), + BuiltInResponseWriters.getWriter("geojson"), is(standardWriter)); } } From de08d3198ab4ade896f6a945acc3151f595e5adb Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 24 Jan 2026 08:25:08 -0500 Subject: [PATCH 06/19] respond to copilot feedback --- .../java/org/apache/solr/response/BuiltInResponseWriters.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index 99b1b57b06a7..e0e26139aaef 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -40,6 +40,10 @@ */ public class BuiltInResponseWriters { + private BuiltInResponseWriters() { + // Prevent instantiation + } + /** * Built-in response writers that are always available. * From a0a9da4979106fcfca158c0ce609085a233b8ccc Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 08:32:54 -0500 Subject: [PATCH 07/19] Instead of a anonymous class living in SolrCore, introduce FileStreamResponseWriter Make the class an explicit class, and add a test. This removes some extra code from SolrCore. --- .../java/org/apache/solr/core/SolrCore.java | 32 +--- .../solr/response/BuiltInResponseWriters.java | 5 +- .../response/FileStreamResponseWriter.java | 66 ++++++++ .../org/apache/solr/servlet/HttpSolrCall.java | 2 +- .../TestFileStreamResponseWriter.java | 149 ++++++++++++++++++ 5 files changed, 221 insertions(+), 33 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java create mode 100644 solr/core/src/test/org/apache/solr/response/TestFileStreamResponseWriter.java 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 84fb501c187c..208858cd4f67 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -110,7 +110,6 @@ import org.apache.solr.handler.IndexFetcher; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.SolrConfigHandler; -import org.apache.solr.handler.admin.api.ReplicationAPIBase; import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.handler.component.HighlightComponent; import org.apache.solr.handler.component.SearchComponent; @@ -128,7 +127,6 @@ import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; import org.apache.solr.response.BuiltInResponseWriters; -import org.apache.solr.response.JavaBinResponseWriter; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.response.transform.TransformerFactory; @@ -3078,31 +3076,6 @@ public PluginBag getResponseWriters() { private final PluginBag responseWriters = new PluginBag<>(QueryResponseWriter.class, this); - private static JavaBinResponseWriter getFileStreamWriter() { - return new JavaBinResponseWriter() { - @Override - 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) { - rawWriter.write(out); - if (rawWriter instanceof Closeable) ((Closeable) rawWriter).close(); - } - } - - @Override - public String getContentType(SolrQueryRequest request, SolrQueryResponse response) { - RawWriter rawWriter = (RawWriter) response.getValues().get(ReplicationAPIBase.FILE_STREAM); - if (rawWriter != null) { - return rawWriter.getContentType(); - } else { - return JavaBinResponseParser.JAVABIN_CONTENT_TYPE; - } - } - }; - } - public void fetchLatestSchema() { IndexSchema schema = configSet.getIndexSchema(true); setLatestSchema(schema); @@ -3117,7 +3090,7 @@ default String getContentType() { } /** - * Gets a response writer suitable for admin/container-level requests. + * Gets a response writer suitable for node/container-level requests. * * @param writerName the writer name, or null for default * @return the response writer, never null @@ -3153,9 +3126,6 @@ private void initWriters() { } } - // Add special filestream writer (custom implementation) - defaultWriters.put(ReplicationAPIBase.FILE_STREAM, getFileStreamWriter()); - // Initialize with the built defaults responseWriters.init(defaultWriters, this); diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index e0e26139aaef..bd3b7dadc6b8 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import org.apache.solr.common.params.CommonParams; +import org.apache.solr.handler.admin.api.ReplicationAPIBase; /** * Registry for built-in response writers in Solr. @@ -54,19 +55,21 @@ private BuiltInResponseWriters() { *
  • json/standard - JSON format, default for most requests *
  • xml - XML format, provides backward compatibility *
  • prometheus/openmetrics - Required by metrics endpoints + *
  • filestream - File streaming for replication and exports * */ private static final Map BUILTIN_WRITERS; static { // Initialize built-in writers that are always available - Map builtinWriters = new HashMap<>(6, 1); + Map builtinWriters = new HashMap<>(7, 1); builtinWriters.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); builtinWriters.put(CommonParams.JSON, new JacksonJsonWriter()); builtinWriters.put("standard", builtinWriters.get(CommonParams.JSON)); // Alias for JSON builtinWriters.put("xml", new XMLResponseWriter()); builtinWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); builtinWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); + builtinWriters.put(ReplicationAPIBase.FILE_STREAM, new FileStreamResponseWriter()); BUILTIN_WRITERS = Collections.unmodifiableMap(builtinWriters); } diff --git a/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java b/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java new file mode 100644 index 000000000000..6ebc5c76013a --- /dev/null +++ b/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java @@ -0,0 +1,66 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.solr.client.solrj.response.JavaBinResponseParser; +import org.apache.solr.core.SolrCore; +import org.apache.solr.handler.admin.api.ReplicationAPIBase; +import org.apache.solr.request.SolrQueryRequest; + +/** + * Response writer for file streaming operations, used for replication, exports, and other core Solr + * operations. + * + *

    This writer handles streaming of large files (such as index files) by looking for a {@link + * SolrCore.RawWriter} object in the response under the {@link ReplicationAPIBase#FILE_STREAM} key. + * When found, it delegates directly to the raw writer to stream the file content efficiently. + * + *

    This writer is specifically designed for replication file transfers and provides no fallback + * behavior - it only works when a proper RawWriter is present in the response. + */ +public class FileStreamResponseWriter implements QueryResponseWriter { + + @Override + public void write( + OutputStream out, SolrQueryRequest request, SolrQueryResponse response, String contentType) + throws IOException { + SolrCore.RawWriter rawWriter = + (SolrCore.RawWriter) response.getValues().get(ReplicationAPIBase.FILE_STREAM); + if (rawWriter != null) { + rawWriter.write(out); + if (rawWriter instanceof Closeable closeable) { + closeable.close(); + } + } + } + + @Override + public String getContentType(SolrQueryRequest request, SolrQueryResponse response) { + SolrCore.RawWriter rawWriter = + (SolrCore.RawWriter) response.getValues().get(ReplicationAPIBase.FILE_STREAM); + if (rawWriter != null) { + String contentType = rawWriter.getContentType(); + if (contentType != null) { + return contentType; + } + } + return JavaBinResponseParser.JAVABIN_CONTENT_TYPE; + } +} 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 634845e0d282..93509768add0 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -736,7 +736,7 @@ protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOExce solrResp.getToLogAsString("[admin]")); } } - // Admin requests have no core, use built-in writers + // node/container requests have no core, use built-in writers QueryResponseWriter respWriter = BuiltInResponseWriters.getWriter(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = getResponseWriter(); diff --git a/solr/core/src/test/org/apache/solr/response/TestFileStreamResponseWriter.java b/solr/core/src/test/org/apache/solr/response/TestFileStreamResponseWriter.java new file mode 100644 index 000000000000..5852aefbe145 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/response/TestFileStreamResponseWriter.java @@ -0,0 +1,149 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import org.apache.solr.SolrTestCase; +import org.apache.solr.client.solrj.response.JavaBinResponseParser; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.core.SolrCore; +import org.apache.solr.handler.admin.api.ReplicationAPIBase; +import org.apache.solr.request.LocalSolrQueryRequest; +import org.apache.solr.request.SolrQueryRequest; +import org.junit.Test; + +public class TestFileStreamResponseWriter extends SolrTestCase { + + @Test + public void testWriteWithRawWriter() throws IOException { + FileStreamResponseWriter writer = new FileStreamResponseWriter(); + SolrQueryRequest request = new LocalSolrQueryRequest(null, new ModifiableSolrParams()); + SolrQueryResponse response = new SolrQueryResponse(); + + // Create a mock RawWriter + String testContent = "test file content"; + TestRawWriter rawWriter = new TestRawWriter(testContent, "application/octet-stream"); + + // Add the RawWriter to the response + response.add(ReplicationAPIBase.FILE_STREAM, rawWriter); + + // Write to output stream + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writer.write(out, request, response, null); + + // Verify the content was written + String written = out.toString(StandardCharsets.UTF_8); + assertEquals("Content should be written directly", testContent, written); + } + + @Test + public void testWriteWithoutRawWriter() throws IOException { + FileStreamResponseWriter writer = new FileStreamResponseWriter(); + SolrQueryRequest request = new LocalSolrQueryRequest(null, new ModifiableSolrParams()); + SolrQueryResponse response = new SolrQueryResponse(); + + // Don't add any RawWriter to the response + + // Write to output stream + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writer.write(out, request, response, null); + + // Verify nothing was written (since no RawWriter present) + assertEquals("Nothing should be written when no RawWriter present", 0, out.size()); + } + + @Test + public void testGetContentTypeWithRawWriter() { + FileStreamResponseWriter writer = new FileStreamResponseWriter(); + SolrQueryRequest request = new LocalSolrQueryRequest(null, new ModifiableSolrParams()); + SolrQueryResponse response = new SolrQueryResponse(); + + // Create a mock RawWriter with custom content type + String customContentType = "application/custom-type"; + TestRawWriter rawWriter = new TestRawWriter("content", customContentType); + + // Add the RawWriter to the response + response.add(ReplicationAPIBase.FILE_STREAM, rawWriter); + + // Get content type + String contentType = writer.getContentType(request, response); + assertEquals("Should return RawWriter's content type", customContentType, contentType); + } + + @Test + public void testGetContentTypeWithoutRawWriter() { + FileStreamResponseWriter writer = new FileStreamResponseWriter(); + SolrQueryRequest request = new LocalSolrQueryRequest(null, new ModifiableSolrParams()); + SolrQueryResponse response = new SolrQueryResponse(); + + // Don't add any RawWriter to the response + + // Get content type + String contentType = writer.getContentType(request, response); + assertEquals( + "Should return default javabin content type", + JavaBinResponseParser.JAVABIN_CONTENT_TYPE, + contentType); + } + + @Test + public void testGetContentTypeWithRawWriterReturningNull() { + FileStreamResponseWriter writer = new FileStreamResponseWriter(); + SolrQueryRequest request = new LocalSolrQueryRequest(null, new ModifiableSolrParams()); + SolrQueryResponse response = new SolrQueryResponse(); + + // Create a mock RawWriter that returns null for content type + TestRawWriter rawWriter = new TestRawWriter("content", null); + + // Add the RawWriter to the response + response.add(ReplicationAPIBase.FILE_STREAM, rawWriter); + + // Get content type + String contentType = writer.getContentType(request, response); + assertEquals( + "Should return default javabin content type when RawWriter returns null", + JavaBinResponseParser.JAVABIN_CONTENT_TYPE, + contentType); + } + + // Test helper classes + // Avoids standing up a full Solr core for this test by mocking. + private static class TestRawWriter implements SolrCore.RawWriter { + private final String content; + private final String contentType; + + public TestRawWriter(String content, String contentType) { + this.content = content; + this.contentType = contentType; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public void write(OutputStream os) throws IOException { + if (content != null) { + os.write(content.getBytes(StandardCharsets.UTF_8)); + } + } + } +} From c7e469352c823fba3b9c4d4a4ec2a49c05b5d6df Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 08:37:24 -0500 Subject: [PATCH 08/19] Fix links. --- .../src/java/org/apache/solr/response/SolrQueryResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/SolrQueryResponse.java b/solr/core/src/java/org/apache/solr/response/SolrQueryResponse.java index f799859a2633..5f2b67622d63 100644 --- a/solr/core/src/java/org/apache/solr/response/SolrQueryResponse.java +++ b/solr/core/src/java/org/apache/solr/response/SolrQueryResponse.java @@ -349,7 +349,7 @@ public boolean isHttpCaching() { * * @param name the name of the header * @param value the header value If it contains octet string, it should be encoded according to - * RFC 2047 (http://www.ietf.org/rfc/rfc2047.txt) + * RFC 2047 (...) * @see #addHttpHeader * @see HttpServletResponse#setHeader */ @@ -364,7 +364,7 @@ public void setHttpHeader(String name, String value) { * * @param name the name of the header * @param value the additional header value If it contains octet string, it should be encoded - * according to RFC 2047 (http://www.ietf.org/rfc/rfc2047.txt) + * according to RFC 2047 (...) * @see #setHttpHeader * @see HttpServletResponse#addHeader */ From e4f3370f43a5b3fa1658bf78ffdf1f9ed5191abd Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 09:02:25 -0500 Subject: [PATCH 09/19] Less wordy javadocs. --- .../org/apache/solr/request/SolrQueryRequest.java | 7 +++---- .../solr/response/BuiltInResponseWriters.java | 15 +++++---------- 2 files changed, 8 insertions(+), 14 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 9cb993098e34..c44b343baceb 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -146,7 +146,7 @@ default String getPath() { /** * Only for V2 API. Returns a map of path segments and their values. For example, if the path is - * configured as /path/{segment1}/{segment2} and a reguest is made as /path/x/y the returned map + * configured as /path/{segment1}/{segment2} and a request is made as /path/x/y the returned map * would contain {segment1:x ,segment2:y} */ default Map getPathTemplateValues() { @@ -199,9 +199,8 @@ default CloudDescriptor getCloudDescriptor() { /** * The writer to use for this request, considering {@link CommonParams#WT}. Never null. * - *

    If a core is available, uses the core's response writer registry (loaded from - * ImplicitPlugins.json with ConfigOverlay support). If no core is available (e.g., for - * admin/container requests), uses a minimal set of admin-appropriate writers. + *

    If a core is available, uses the core's response writer registry. If no core is available + * (e.g., for node/container requests), uses a minimal set of node/container-appropriate writers. */ default QueryResponseWriter getResponseWriter() { // it's weird this method is here instead of SolrQueryResponse, but it's practical/convenient diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index bd3b7dadc6b8..f474d3465a00 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -26,18 +26,13 @@ import org.apache.solr.handler.admin.api.ReplicationAPIBase; /** - * Registry for built-in response writers in Solr. + * Essential response writers always available regardless of core configuration. * - *

    Manages a minimal set of essential response writers that are always available, regardless of - * core configuration or ImplicitPlugins.json settings. These writers are primarily used by - * admin/container-level requests that have no associated SolrCore. + *

    Used by node/container-level requests that have no associated {@link + * org.apache.solr.core.SolrCore}. * - *

    Built-in writers include essential formats needed by admin APIs and core functionality: - * javabin, json, xml, prometheus, and openmetrics. - * - *

    For core-specific requests that need access to the full set of response writers (including - * csv, geojson, graphml, smile, etc.), use the SolrCore's response writer registry which is loaded - * from ImplicitPlugins.json and supports ConfigOverlay customizations. + *

    For the full set of response writers (csv, geojson, graphml, smile, etc.), use {@link + * org.apache.solr.core.SolrCore}'s response writer registry. */ public class BuiltInResponseWriters { From 4a9745d33796f0d3231edcaf1c30624d70fe739c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 09:14:08 -0500 Subject: [PATCH 10/19] back out ranting comments in favour of new JIRA --- .../org/apache/solr/response/BuiltInResponseWriters.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index f474d3465a00..a926d593098b 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -81,11 +81,6 @@ private BuiltInResponseWriters() { * @return the response writer, never null (returns "standard"/JSON if not found) */ public static QueryResponseWriter getWriter(String writerName) { - // carrying over this "standard" thing from original code, but do we want this? null/blank - // means standard? - // feels like null or blank should throw an exception. And a method should be added that is - // getWriter() that - // returns the json guy. I hate passing around nulls and ""... if (writerName == null || writerName.isEmpty()) { return BUILTIN_WRITERS.get("standard"); } From 95b9fea8ccd059fe202d15ffc462b28c1e7d67ff Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 09:18:48 -0500 Subject: [PATCH 11/19] use modern Map.of --- .../solr/response/BuiltInResponseWriters.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index a926d593098b..ecf4984629ce 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -19,8 +19,6 @@ import static org.apache.solr.handler.admin.MetricsHandler.OPEN_METRICS_WT; import static org.apache.solr.handler.admin.MetricsHandler.PROMETHEUS_METRICS_WT; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.apache.solr.common.params.CommonParams; import org.apache.solr.handler.admin.api.ReplicationAPIBase; @@ -57,15 +55,25 @@ private BuiltInResponseWriters() { static { // Initialize built-in writers that are always available - Map builtinWriters = new HashMap<>(7, 1); - builtinWriters.put(CommonParams.JAVABIN, new JavaBinResponseWriter()); - builtinWriters.put(CommonParams.JSON, new JacksonJsonWriter()); - builtinWriters.put("standard", builtinWriters.get(CommonParams.JSON)); // Alias for JSON - builtinWriters.put("xml", new XMLResponseWriter()); - builtinWriters.put(PROMETHEUS_METRICS_WT, new PrometheusResponseWriter()); - builtinWriters.put(OPEN_METRICS_WT, new PrometheusResponseWriter()); - builtinWriters.put(ReplicationAPIBase.FILE_STREAM, new FileStreamResponseWriter()); - BUILTIN_WRITERS = Collections.unmodifiableMap(builtinWriters); + JacksonJsonWriter jsonWriter = new JacksonJsonWriter(); + 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()); } /** From 513ad20ee751c615acfa18649cef281e02c42f31 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 09:23:21 -0500 Subject: [PATCH 12/19] fix precheck --- .../org/apache/solr/response/FileStreamResponseWriter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java b/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java index 6ebc5c76013a..91f6ee12f331 100644 --- a/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java +++ b/solr/core/src/java/org/apache/solr/response/FileStreamResponseWriter.java @@ -29,8 +29,9 @@ * operations. * *

    This writer handles streaming of large files (such as index files) by looking for a {@link - * SolrCore.RawWriter} object in the response under the {@link ReplicationAPIBase#FILE_STREAM} key. - * When found, it delegates directly to the raw writer to stream the file content efficiently. + * org.apache.solr.core.SolrCore.RawWriter} object in the response under the {@link + * ReplicationAPIBase#FILE_STREAM} key. When found, it delegates directly to the raw writer to + * stream the file content efficiently. * *

    This writer is specifically designed for replication file transfers and provides no fallback * behavior - it only works when a proper RawWriter is present in the response. From 6741fe65b30d99205e25c6c5bb67f010ace5a54c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 09:26:58 -0500 Subject: [PATCH 13/19] Clean up changelog --- .../admin-response-writers-minimal-set.yml | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/changelog/unreleased/admin-response-writers-minimal-set.yml b/changelog/unreleased/admin-response-writers-minimal-set.yml index 2f26035ecca0..31f62e351984 100644 --- a/changelog/unreleased/admin-response-writers-minimal-set.yml +++ b/changelog/unreleased/admin-response-writers-minimal-set.yml @@ -1,22 +1,6 @@ # See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc -title: Introduce minimal ADMIN_RESPONSE_WRITERS for admin/container-level requests. Admin requests now use a minimal set of 6 response writers (javabin, json, xml, prometheus, openmetrics, standard) instead of the full 14-writer DEFAULT_RESPONSE_WRITERS map. Core-specific requests continue to use ImplicitPlugins.json with ConfigOverlay support. +title: Introduce minimal set of request writers for node/container-level requests. Core-specific request writers now leverage ImplicitPlugins.json for creation. type: changed authors: - name: Eric Pugh -links: - - name: GitHub Discussion - url: https://github.com/apache/solr/discussions -notes: | - Admin/container-level requests (e.g., /admin/cores, /admin/collections) have no associated SolrCore - and cannot use the core's response writer registry loaded from ImplicitPlugins.json. Previously, - these requests used DEFAULT_RESPONSE_WRITERS (14 entries). Now they use ADMIN_RESPONSE_WRITERS (6 entries) - containing only the formats actually needed by admin APIs: - - javabin (SolrJ clients) - - json (Admin UI) - - xml (backward compatibility) - - prometheus/openmetrics (metrics endpoint) - - standard (alias for json) - - This provides clearer separation of concerns and better documentation of requirements. Core-specific - requests are unaffected and continue to use the full ImplicitPlugins.json system with ConfigOverlay support. - DEFAULT_RESPONSE_WRITERS remains available but is now deprecated for internal use. \ No newline at end of file + - name: David Smiley From 816c92b5ca5f0743a576b734ed438f12325dea9e Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 25 Jan 2026 15:39:03 -0500 Subject: [PATCH 14/19] dont duplicate requestwriters in the ImplicitPlugins --- .../java/org/apache/solr/core/SolrCore.java | 5 ++++- .../solr/response/BuiltInResponseWriters.java | 9 ++++++++ solr/core/src/resources/ImplicitPlugins.json | 21 ------------------- 3 files changed, 13 insertions(+), 22 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 208858cd4f67..9520d265a6b6 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -3109,7 +3109,10 @@ private void initWriters() { // Build default writers map from implicit plugins Map defaultWriters = new HashMap<>(); - // Load writers from ImplicitPlugins.json + // Start with built-in writers that are always available + defaultWriters.putAll(BuiltInResponseWriters.getAllWriters()); + + // Load writers from ImplicitPlugins.json (may override built-ins) List implicitWriters = getImplicitResponseWriters(); for (PluginInfo info : implicitWriters) { try { diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index ecf4984629ce..501ffe1fc555 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -94,4 +94,13 @@ public static QueryResponseWriter getWriter(String writerName) { } return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard")); } + + /** + * Gets all built-in response writers. + * + * @return immutable map of all built-in writers + */ + public static Map getAllWriters() { + return BUILTIN_WRITERS; + } } diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json index dfd3904da09d..eba9bd05ba0e 100644 --- a/solr/core/src/resources/ImplicitPlugins.json +++ b/solr/core/src/resources/ImplicitPlugins.json @@ -159,27 +159,12 @@ } }, "queryResponseWriter": { - "xml": { - "class": "solr.XMLResponseWriter" - }, - "json": { - "class": "solr.JacksonJsonWriter" - }, - "standard": { - "class": "solr.JacksonJsonWriter" - }, "geojson": { "class": "solr.GeoJSONResponseWriter" }, "graphml": { "class": "solr.GraphMLResponseWriter" }, - "raw": { - "class": "solr.RawResponseWriter" - }, - "javabin": { - "class": "solr.JavaBinResponseWriter" - }, "cbor": { "class": "solr.CborResponseWriter" }, @@ -191,12 +176,6 @@ }, "smile": { "class": "solr.SmileResponseWriter" - }, - "prometheus": { - "class": "solr.PrometheusResponseWriter" - }, - "openmetrics": { - "class": "solr.PrometheusResponseWriter" } } } From f2364d3ced635c3f35493170b667ebcb931ad099 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 07:49:15 -0500 Subject: [PATCH 15/19] Responding to feedback. --- .../admin-response-writers-minimal-set.yml | 2 +- .../solr/response/BuiltInResponseWriters.java | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/changelog/unreleased/admin-response-writers-minimal-set.yml b/changelog/unreleased/admin-response-writers-minimal-set.yml index 31f62e351984..9c7985c9d350 100644 --- a/changelog/unreleased/admin-response-writers-minimal-set.yml +++ b/changelog/unreleased/admin-response-writers-minimal-set.yml @@ -1,6 +1,6 @@ # See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc title: Introduce minimal set of request writers for node/container-level requests. Core-specific request writers now leverage ImplicitPlugins.json for creation. -type: changed +type: other authors: - name: Eric Pugh - name: David Smiley diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java index 501ffe1fc555..51622cec16c5 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java @@ -38,19 +38,6 @@ private BuiltInResponseWriters() { // Prevent instantiation } - /** - * Built-in response writers that are always available. - * - *

    Contains only essential formats needed by admin APIs and core functionality: - * - *

      - *
    • javabin - Binary format, efficient for SolrJ clients - *
    • json/standard - JSON format, default for most requests - *
    • xml - XML format, provides backward compatibility - *
    • prometheus/openmetrics - Required by metrics endpoints - *
    • filestream - File streaming for replication and exports - *
    - */ private static final Map BUILTIN_WRITERS; static { From 45704e1cf5032eebda8a1756701c2a3d51320cdf Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 07:51:21 -0500 Subject: [PATCH 16/19] Rename to ResponseWritersRegistry --- .../src/java/org/apache/solr/core/SolrCore.java | 8 ++++---- .../org/apache/solr/request/SolrQueryRequest.java | 4 ++-- ...seWriters.java => ResponseWritersRegistry.java} | 6 +++--- .../java/org/apache/solr/servlet/HttpSolrCall.java | 4 ++-- .../org/apache/solr/core/TestImplicitPlugins.java | 14 +++++++------- ...iters.java => TestResponseWritersRegistry.java} | 14 +++++++------- 6 files changed, 25 insertions(+), 25 deletions(-) rename solr/core/src/java/org/apache/solr/response/{BuiltInResponseWriters.java => ResponseWritersRegistry.java} (95%) rename solr/core/src/test/org/apache/solr/response/{TestBuiltInResponseWriters.java => TestResponseWritersRegistry.java} (81%) 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 9520d265a6b6..8151a56bb71a 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -126,7 +126,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.BuiltInResponseWriters; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.response.transform.TransformerFactory; @@ -3094,11 +3094,11 @@ default String getContentType() { * * @param writerName the writer name, or null for default * @return the response writer, never null - * @deprecated Use {@link BuiltInResponseWriters#getWriter(String)} instead. + * @deprecated Use {@link ResponseWritersRegistry#getWriter(String)} instead. */ @Deprecated public static QueryResponseWriter getAdminResponseWriter(String writerName) { - return BuiltInResponseWriters.getWriter(writerName); + return ResponseWritersRegistry.getWriter(writerName); } /** @@ -3110,7 +3110,7 @@ private void initWriters() { Map defaultWriters = new HashMap<>(); // Start with built-in writers that are always available - defaultWriters.putAll(BuiltInResponseWriters.getAllWriters()); + defaultWriters.putAll(ResponseWritersRegistry.getAllWriters()); // Load writers from ImplicitPlugins.json (may override built-ins) List implicitWriters = getImplicitResponseWriters(); 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 c44b343baceb..7a43bc82a0d5 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -30,7 +30,7 @@ import org.apache.solr.common.util.EnvUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.SolrCore; -import org.apache.solr.response.BuiltInResponseWriters; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.schema.IndexSchema; import org.apache.solr.search.SolrIndexSearcher; @@ -210,7 +210,7 @@ default QueryResponseWriter getResponseWriter() { if (core != null) { return core.getQueryResponseWriter(wt); } else { - return BuiltInResponseWriters.getWriter(wt); + return ResponseWritersRegistry.getWriter(wt); } } diff --git a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java similarity index 95% rename from solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java rename to solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java index 51622cec16c5..848bcc2a7dd6 100644 --- a/solr/core/src/java/org/apache/solr/response/BuiltInResponseWriters.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -29,12 +29,12 @@ *

    Used by node/container-level requests that have no associated {@link * org.apache.solr.core.SolrCore}. * - *

    For the full set of response writers (csv, geojson, graphml, smile, etc.), use {@link + *

    For the full set of response writers see {@link * org.apache.solr.core.SolrCore}'s response writer registry. */ -public class BuiltInResponseWriters { +public class ResponseWritersRegistry { - private BuiltInResponseWriters() { + private ResponseWritersRegistry() { // Prevent instantiation } 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 93509768add0..f811e2b34914 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -85,7 +85,7 @@ import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.BuiltInResponseWriters; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuditEvent; @@ -738,7 +738,7 @@ protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOExce } // node/container requests have no core, use built-in writers QueryResponseWriter respWriter = - BuiltInResponseWriters.getWriter(solrReq.getParams().get(CommonParams.WT)); + ResponseWritersRegistry.getWriter(solrReq.getParams().get(CommonParams.WT)); if (respWriter == null) respWriter = getResponseWriter(); writeResponse(solrResp, respWriter, Method.getMethod(req.getMethod())); if (shouldAudit()) { diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java index 6158d811fdad..8d598b9e5f0a 100644 --- a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -18,7 +18,7 @@ import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.CommonParams; -import org.apache.solr.response.BuiltInResponseWriters; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; import org.junit.BeforeClass; import org.junit.Test; @@ -31,7 +31,7 @@ *

      *
    • Request handlers are loaded from ImplicitPlugins.json *
    • Response writers are loaded from ImplicitPlugins.json for core requests - *
    • Built in response writers use a minimal set defined in {@link BuiltInResponseWriters}. + *
    • Built in response writers use a minimal set defined in {@link ResponseWritersRegistry}. *
    */ public class TestImplicitPlugins extends SolrTestCaseJ4 { @@ -54,24 +54,24 @@ public void testCoreAndBuiltInWriterIntegration() { assertNotNull("Core should have smile writer", core.getQueryResponseWriter("smile")); // Test that built-in registry has minimal set and falls back for extended formats - QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); + QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("standard"); assertSame( "Built-in csv request should fall back to standard", standardWriter, - BuiltInResponseWriters.getWriter("csv")); + ResponseWritersRegistry.getWriter("csv")); assertSame( "Built-in geojson request should fall back to standard", standardWriter, - BuiltInResponseWriters.getWriter("geojson")); + ResponseWritersRegistry.getWriter("geojson")); // Test that both systems have common essential formats (though may be different instances) QueryResponseWriter coreJsonWriter = core.getQueryResponseWriter(CommonParams.JSON); - QueryResponseWriter builtInJsonWriter = BuiltInResponseWriters.getWriter(CommonParams.JSON); + QueryResponseWriter builtInJsonWriter = ResponseWritersRegistry.getWriter(CommonParams.JSON); assertNotNull("Core json writer should not be null", coreJsonWriter); assertNotNull("Built-in json writer should not be null", builtInJsonWriter); QueryResponseWriter coreXmlWriter = core.getQueryResponseWriter("xml"); - QueryResponseWriter builtInXmlWriter = BuiltInResponseWriters.getWriter("xml"); + QueryResponseWriter builtInXmlWriter = ResponseWritersRegistry.getWriter("xml"); assertNotNull("Core xml writer should not be null", coreXmlWriter); assertNotNull("Built-in xml writer should not be null", builtInXmlWriter); } diff --git a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java b/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java similarity index 81% rename from solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java rename to solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java index 60115a89be36..695ad0e1278c 100644 --- a/solr/core/src/test/org/apache/solr/response/TestBuiltInResponseWriters.java +++ b/solr/core/src/test/org/apache/solr/response/TestResponseWritersRegistry.java @@ -27,38 +27,38 @@ * This test validates the registry's behavior for built-in response writers, including * availability, fallback behavior, and proper format handling. Notice there is no core configured! */ -public class TestBuiltInResponseWriters extends SolrTestCaseJ4 { +public class TestResponseWritersRegistry extends SolrTestCaseJ4 { @Test public void testBuiltInWriterFallbackBehavior() { - QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); + QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("standard"); // Test null fallback - QueryResponseWriter nullWriter = BuiltInResponseWriters.getWriter(null); + 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)); // Test empty string fallback - QueryResponseWriter emptyWriter = BuiltInResponseWriters.getWriter(""); + 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)); // Test unknown format fallback - QueryResponseWriter unknownWriter = BuiltInResponseWriters.getWriter("nonexistent"); + 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)); } @Test public void testBuiltInWriterLimitedSet() { - QueryResponseWriter standardWriter = BuiltInResponseWriters.getWriter("standard"); + QueryResponseWriter standardWriter = ResponseWritersRegistry.getWriter("standard"); // 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! assertThat( "geojson should fall back to standard", - BuiltInResponseWriters.getWriter("geojson"), + ResponseWritersRegistry.getWriter("geojson"), is(standardWriter)); } } From 1ed188507614d3d075bea4a12930c1fa99adb444 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 07:51:49 -0500 Subject: [PATCH 17/19] tidy --- solr/core/src/java/org/apache/solr/core/SolrCore.java | 2 +- .../src/java/org/apache/solr/request/SolrQueryRequest.java | 2 +- .../org/apache/solr/response/ResponseWritersRegistry.java | 4 ++-- solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java | 2 +- .../src/test/org/apache/solr/core/TestImplicitPlugins.java | 2 +- 5 files changed, 6 insertions(+), 6 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 8151a56bb71a..bab4359d87d6 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -126,8 +126,8 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.response.transform.TransformerFactory; import org.apache.solr.rest.ManagedResourceStorage; 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 7a43bc82a0d5..0ce4d82e5516 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequest.java @@ -30,8 +30,8 @@ import org.apache.solr.common.util.EnvUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.SolrCore; -import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.schema.IndexSchema; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.servlet.HttpSolrCall; 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 848bcc2a7dd6..cfd04e28714e 100644 --- a/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java +++ b/solr/core/src/java/org/apache/solr/response/ResponseWritersRegistry.java @@ -29,8 +29,8 @@ *

    Used by node/container-level requests that have no associated {@link * org.apache.solr.core.SolrCore}. * - *

    For the full set of response writers see {@link - * org.apache.solr.core.SolrCore}'s response writer registry. + *

    For the full set of response writers see {@link org.apache.solr.core.SolrCore}'s response + * writer registry. */ public class ResponseWritersRegistry { 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 f811e2b34914..1229aed8d0a0 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -85,8 +85,8 @@ import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; -import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; +import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuditEvent; import org.apache.solr.security.AuditEvent.EventType; diff --git a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java index 8d598b9e5f0a..a04604b22d7d 100644 --- a/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java +++ b/solr/core/src/test/org/apache/solr/core/TestImplicitPlugins.java @@ -18,8 +18,8 @@ import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.CommonParams; -import org.apache.solr.response.ResponseWritersRegistry; import org.apache.solr.response.QueryResponseWriter; +import org.apache.solr.response.ResponseWritersRegistry; import org.junit.BeforeClass; import org.junit.Test; From f88abea4f7875711a5362cff8ba90f01198cd70c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 07:53:22 -0500 Subject: [PATCH 18/19] Provide link to the PR --- changelog/unreleased/admin-response-writers-minimal-set.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog/unreleased/admin-response-writers-minimal-set.yml b/changelog/unreleased/admin-response-writers-minimal-set.yml index 9c7985c9d350..64b59f93ab65 100644 --- a/changelog/unreleased/admin-response-writers-minimal-set.yml +++ b/changelog/unreleased/admin-response-writers-minimal-set.yml @@ -4,3 +4,6 @@ type: other authors: - name: Eric Pugh - name: David Smiley +links: +- name: PR#4073 + url: https://github.com/apache/solr/pull/4073 From fe51a67592622abf929b16fe942eb1a7da90f1ba Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 27 Jan 2026 22:03:56 -0500 Subject: [PATCH 19/19] Small doc fixes. --- .../org/apache/solr/handler/admin/SystemInfoHandler.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java index 16e78ab4268b..18447eafac9c 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java @@ -77,9 +77,8 @@ public class SystemInfoHandler extends RequestHandlerBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** - * Undocumented expert level system property to prevent doing a reverse lookup of our hostname. - * This property will be logged as a suggested workaround if any problems are noticed when doing - * reverse lookup. + * Expert level system property to prevent doing a reverse lookup of our hostname. This property + * will be logged as a suggested workaround if any problems are noticed when doing reverse lookup. * *

    TODO: should we refactor this (and the associated logic) into a helper method for any other * places where DNS is used? @@ -97,7 +96,7 @@ public class SystemInfoHandler extends RequestHandlerBase { private static final ConcurrentMap, BeanInfo> beanInfos = new ConcurrentHashMap<>(); // on some platforms, resolving canonical hostname can cause the thread - // to block for several seconds if nameservices aren't available + // to block for several seconds if name services aren't available // so resolve this once per handler instance // (ie: not static, so core reload will refresh) private String hostname = null;