Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/SOLR-18085.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Removed the wt=standard concept that was used internally by Solr.
type: removed # added, changed, fixed, deprecated, removed, dependency_update, security, other
authors:
- name: Eric Pugh
links:
- name: SOLR-18085
url: https://issues.apache.org/jira/browse/SOLR-18085
2 changes: 1 addition & 1 deletion solr/core/src/java/org/apache/solr/core/PluginBag.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public T get(String name) {
* Fetches a plugin by name , or the default
*
* @param name name using which it is registered
* @param useDefault Return the default , if a plugin by that name does not exist
* @param useDefault Return the default, if a plugin by that name does not exist
*/
public T get(String name, boolean useDefault) {
T result = get(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ void initHandlersFromConfig(SolrConfig config) {
modifiedInfos.add(applyInitParams(config, info));
}
handlers.init(Collections.emptyMap(), core, modifiedInfos);
handlers.alias(handlers.getDefault(), "");

if (log.isDebugEnabled()) {
log.debug("Registered paths: {}", StrUtils.join(new ArrayList<>(handlers.keySet()), ','));
}
Expand Down
7 changes: 2 additions & 5 deletions solr/core/src/java/org/apache/solr/core/SolrCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -3131,14 +3131,11 @@ private void initWriters() {

// Initialize with the built defaults
responseWriters.init(defaultWriters, this);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

initWriters() no longer ensures a default response writer is set on responseWriters. If no <queryResponseWriter default="true"> is defined in solrconfig (e.g., configsets where response writers are only implicit), PluginBag's default (def) can remain null, which breaks default writer resolution and can change the default output unexpectedly. Consider restoring a safe default (e.g., set default to json) when responseWriters.getDefault() is null after init.

Suggested change
responseWriters.init(defaultWriters, this);
responseWriters.init(defaultWriters, this);
// Ensure there is always a default response writer configured.
// If no <queryResponseWriter default="true"> is defined and PluginBag
// did not pick a default, fall back to "json" when available.
if (responseWriters.getDefault() == null) {
QueryResponseWriter jsonWriter = responseWriters.get("json", false);
if (jsonWriter != null) {
responseWriters.setDefault("json");
}
}

Copilot uses AI. Check for mistakes.

// configure the default response writer; this one should never be null
if (responseWriters.getDefault() == null) responseWriters.setDefault("standard");
}

/** Finds a writer by name, or returns the default writer if not found. */
/** Finds a writer by name, or null if not found. */
public final QueryResponseWriter getQueryResponseWriter(String writerName) {
return responseWriters.get(writerName, true);
return responseWriters.get(writerName, false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,7 @@ default CloudDescriptor getCloudDescriptor() {
*/
default QueryResponseWriter getResponseWriter() {
// it's weird this method is here instead of SolrQueryResponse, but it's practical/convenient
SolrCore core = getCore();
String wt = getParams().get(CommonParams.WT);
// Use core writers if available, otherwise fall back to built-in writers
if (core != null) {
return core.getQueryResponseWriter(wt);
} else {
return ResponseWritersRegistry.getWriter(wt);
}
return ResponseWritersRegistry.getWriter(getParams().get(CommonParams.WT), getCore());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class RawResponseWriter implements QueryResponseWriter {
*/
public static final String CONTENT = "content";

private String _baseWriter = null;
private String baseWriter = null;

/**
* A fallback writer used for requests that don't return raw content and that aren't associated
Expand All @@ -62,14 +62,17 @@ public void init(NamedList<?> n) {
if (n != null) {
Object base = n.get("base");
if (base != null) {
_baseWriter = base.toString();
baseWriter = base.toString();
}
}
}

protected QueryResponseWriter getBaseWriter(SolrQueryRequest request) {
if (request.getCore() != null) {
return request.getCore().getQueryResponseWriter(_baseWriter);
// When baseWriter is null, use the core's default writer (useDefault=true)
// Otherwise, look up the specific writer by name (useDefault=false for explicit lookups)
boolean useDefault = (baseWriter == null);
return request.getCore().getResponseWriters().get(baseWriter, useDefault);
}

// Requests to a specific core already have writers, but we still need a 'default writer' for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import static org.apache.solr.util.stats.MetricUtils.PROMETHEUS_METRICS_WT;

import java.util.Map;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.admin.api.ReplicationAPIBase;

/**
Expand All @@ -46,21 +48,14 @@ private ResponseWritersRegistry() {
PrometheusResponseWriter prometheusWriter = new PrometheusResponseWriter();

BUILTIN_WRITERS =
Map.of(
CommonParams.JAVABIN,
new JavaBinResponseWriter(),
CommonParams.JSON,
jsonWriter,
"standard",
jsonWriter, // Alias for JSON
"xml",
new XMLResponseWriter(),
PROMETHEUS_METRICS_WT,
prometheusWriter,
OPEN_METRICS_WT,
prometheusWriter,
ReplicationAPIBase.FILE_STREAM,
new FileStreamResponseWriter());
Map.ofEntries(
Copy link
Contributor

Choose a reason for hiding this comment

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

nice and tidy :-)

Map.entry(CommonParams.JAVABIN, new JavaBinResponseWriter()),
Map.entry(CommonParams.JSON, jsonWriter),
Map.entry("xml", new XMLResponseWriter()),
Map.entry("raw", new RawResponseWriter()),
Map.entry(PROMETHEUS_METRICS_WT, prometheusWriter),
Map.entry(OPEN_METRICS_WT, prometheusWriter),
Map.entry(ReplicationAPIBase.FILE_STREAM, new FileStreamResponseWriter()));
}

/**
Expand All @@ -69,17 +64,68 @@ private ResponseWritersRegistry() {
* <p>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.
*
* <p>If the requested writer is not available, returns the "standard" (JSON) writer as a
* fallback. This ensures requests always get a valid response format.
* <p>If the requested writer is not available, returns the JSON writer as a fallback. This
* ensures requests always get a valid response format.
*
* @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default
* @return the response writer, never null (returns "standard"/JSON if not found)
* @return the response writer, never null (returns JSON if not found)
*/
public static QueryResponseWriter getWriter(String writerName) {
if (writerName == null || writerName.isEmpty()) {
return BUILTIN_WRITERS.get("standard");
writerName = CommonParams.JSON;
}
return BUILTIN_WRITERS.getOrDefault(writerName, BUILTIN_WRITERS.get("standard"));
return BUILTIN_WRITERS.get(writerName);
}

/**
* Gets a response writer, trying the core's registry first, then falling back to built-in
* writers. This is the unified entry point for all writer resolution.
*
* <p>Resolution order:
*
* <ol>
* <li>If core is provided, check core's writer registry
* <li>If not found in core (or no core), check built-in writers
* <li>If writer name is explicitly specified but not found anywhere, throw exception
* <li>If writer name is null/empty, return default (JSON)
* </ol>
*
* @param writerName the writer name (e.g., "json", "xml", "javabin"), or null for default
* @param core the SolrCore to check first, or null for node-level requests
* @return the response writer, never null
* @throws SolrException if an explicitly requested writer type is not found
*/
public static QueryResponseWriter getWriter(String writerName, SolrCore core) {
QueryResponseWriter writer = null;

// Try core registry first if available
if (core != null) {
writer = core.getQueryResponseWriter(writerName);
}

// If not found and writer is explicitly requested, validate it exists in built-in
if (writer == null) {
if (!hasWriter(writerName)) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Unknown response writer type: " + writerName);
} else {
writer = getWriter(writerName);
}
}
return writer;
}

/**
* Checks if a writer with the given name exists in the built-in writers.
*
* @param writerName the writer name to check
* @return true if the writer exists, false otherwise
*/
public static boolean hasWriter(String writerName) {
if (writerName == null || writerName.isEmpty()) {
return true; // null/empty is valid, will use default
}
return BUILTIN_WRITERS.containsKey(writerName);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ private void handleAdminRequest() throws IOException {

protected void logAndFlushAdminRequest(SolrQueryResponse solrResp) throws IOException {
if (solrResp.getToLog().size() > 0) {
// has to come second and in it's own if to keep ./gradlew check happy.
// has to come second and in its own "if" to keep ./gradlew check happy.
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: I prefer that we don't change source files for out-of-scope reasons that we aren't already touching for in-scope reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I really struggle with seeing these things and not fixing them. I will try not to do it in the futre. I swear I went through the entire code base and fixed all the typos, and yet I keep finding them.

if (log.isInfoEnabled()) {
log.info(
handler != null
Expand Down
56 changes: 14 additions & 42 deletions solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these changes in-scope?

Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,14 @@ public class SolrRequestParsers {

private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

// Should these constants be in a more public place?
public static final String MULTIPART = "multipart";
public static final String FORMDATA = "formdata";
public static final String RAW = "raw";
public static final String SIMPLE = "simple";
public static final String STANDARD = "standard";

private static final Charset CHARSET_US_ASCII = StandardCharsets.US_ASCII;

public static final String INPUT_ENCODING_KEY = "ie";
private static final byte[] INPUT_ENCODING_BYTES = INPUT_ENCODING_KEY.getBytes(CHARSET_US_ASCII);

public static final String REQUEST_TIMER_SERVLET_ATTRIBUTE = "org.apache.solr.RequestTimer";

private final HashMap<String, SolrRequestParser> parsers = new HashMap<>();
private StandardRequestParser standard;
private StandardRequestParser parser;

/**
* Default instance for e.g. admin requests. Limits to 2 MB uploads and does not allow remote
Expand All @@ -91,7 +83,7 @@ public class SolrRequestParsers {
public static final SolrRequestParsers DEFAULT = new SolrRequestParsers();

/**
* Pass in an xml configuration. A null configuration will enable everything with maximum values.
* Pass in a xml configuration. A null configuration will enable everything with maximum values.
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Grammar in this Javadoc is off: "Pass in a xml configuration" should be "Pass in an XML configuration" (and typically XML is capitalized).

Suggested change
* Pass in a xml configuration. A null configuration will enable everything with maximum values.
* Pass in an XML configuration. A null configuration will enable everything with maximum values.

Copilot uses AI. Check for mistakes.
*/
public SolrRequestParsers(SolrConfig globalConfig) {
final int multipartUploadLimitKB, formUploadLimitKB;
Expand All @@ -116,21 +108,12 @@ private void init(int multipartUploadLimitKB, int formUploadLimitKB) {
MultipartRequestParser multi = new MultipartRequestParser(multipartUploadLimitKB);
RawRequestParser raw = new RawRequestParser();
FormDataRequestParser formdata = new FormDataRequestParser(formUploadLimitKB);
standard = new StandardRequestParser(multi, raw, formdata);

// I don't see a need to have this publicly configured just yet
// adding it is trivial
parsers.put(MULTIPART, multi);
parsers.put(FORMDATA, formdata);
parsers.put(RAW, raw);
parsers.put(SIMPLE, new SimpleRequestParser());
parsers.put(STANDARD, standard);
parsers.put("", standard);
parser = new StandardRequestParser(multi, raw, formdata);
}

private static RTimerTree getRequestTimer(HttpServletRequest req) {
final Object reqTimer = req.getAttribute(REQUEST_TIMER_SERVLET_ATTRIBUTE);
if (reqTimer != null && reqTimer instanceof RTimerTree) {
if (reqTimer instanceof RTimerTree) {
return ((RTimerTree) reqTimer);
}

Expand All @@ -139,7 +122,6 @@ private static RTimerTree getRequestTimer(HttpServletRequest req) {

public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req)
throws Exception {
SolrRequestParser parser = standard;

// TODO -- in the future, we could pick a different parser based on the request

Expand All @@ -164,13 +146,12 @@ public SolrQueryRequest parse(SolrCore core, String path, HttpServletRequest req

/** For embedded Solr use; not related to HTTP. */
public SolrQueryRequest buildRequestFrom(
SolrCore core, SolrParams params, Collection<ContentStream> streams) throws Exception {
SolrCore core, SolrParams params, Collection<ContentStream> streams) {
return buildRequestFrom(core, params, streams, new RTimerTree(), null, null);
}

public SolrQueryRequest buildRequestFrom(
SolrCore core, SolrParams params, Collection<ContentStream> streams, Principal principal)
throws Exception {
SolrCore core, SolrParams params, Collection<ContentStream> streams, Principal principal) {
return buildRequestFrom(core, params, streams, new RTimerTree(), null, principal);
}

Expand All @@ -181,7 +162,7 @@ private SolrQueryRequest buildRequestFrom(
RTimerTree requestTimer,
final HttpServletRequest req,
final Principal principal) // from req, if req was provided, otherwise from elsewhere
throws Exception {
{
// ensure streams is non-null and mutable so we can easily add to it
if (streams == null) {
streams = new ArrayList<>();
Expand Down Expand Up @@ -213,7 +194,7 @@ public List<CommandOperation> getCommands(boolean validateInput) {

@Override
public Map<String, String> getPathTemplateValues() {
if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) {
if (httpSolrCall instanceof V2HttpCall) {
return ((V2HttpCall) httpSolrCall).getUrlParts();
}
return super.getPathTemplateValues();
Expand Down Expand Up @@ -337,9 +318,9 @@ static long parseFormDataContent(
// we have no charset decoder until now, buffer the keys / values for later
// processing:
buffer.add(keyBytes);
buffer.add(Long.valueOf(keyPos));
buffer.add(keyPos);
buffer.add(valueBytes);
buffer.add(Long.valueOf(valuePos));
buffer.add(valuePos);
} else {
// we already have a charsetDecoder, so we can directly decode without buffering:
final String key = decodeChars(keyBytes, keyPos, charsetDecoder),
Expand Down Expand Up @@ -457,7 +438,7 @@ private static int digit16(int b) {
// -----------------------------------------------------------------
// -----------------------------------------------------------------

// I guess we don't really even need the interface, but i'll keep it here just for kicks
// I guess we don't really even need the interface, but I'll keep it here just for kicks
interface SolrRequestParser {
public SolrParams parseParamsAndFillStreams(
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception;
Expand All @@ -466,15 +447,6 @@ public SolrParams parseParamsAndFillStreams(
// -----------------------------------------------------------------
// -----------------------------------------------------------------

/** The simple parser just uses the params directly, does not support POST URL-encoded forms */
static class SimpleRequestParser implements SolrRequestParser {
@Override
public SolrParams parseParamsAndFillStreams(
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception {
return parseQueryString(req.getQueryString());
}
}

/** Wrap an HttpServletRequest as a ContentStream */
static class HttpRequestContentStream extends ContentStreamBase {
private final InputStream inputStream;
Expand Down Expand Up @@ -515,7 +487,7 @@ public SolrParams parseParamsAndFillStreams(
|| req.getHeader("Transfer-Encoding") != null
|| !NO_BODY_METHODS.contains(req.getMethod())) {
// If Content-Length > 0 OR Transfer-Encoding exists OR
// it's a method that can have a body (POST/PUT/PATCH etc)
// it's a method that can have a body (POST/PUT/PATCH etc.)
streams.add(new HttpRequestContentStream(req, req.getInputStream()));
}

Expand Down Expand Up @@ -543,7 +515,7 @@ public SolrParams parseParamsAndFillStreams(
throw new SolrException(
ErrorCode.BAD_REQUEST, "Not multipart content! " + req.getContentType());
}
// Magic way to tell Jetty dynamically we want multi-part processing.
// Magic way to tell Jetty dynamically we want multipart processing.
// This is taken from:
// https://github.com/eclipse/jetty.project/blob/jetty-10.0.12/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java#L144
req.setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement);
Expand Down Expand Up @@ -735,7 +707,7 @@ static class StandardRequestParser implements SolrRequestParser {
public SolrParams parseParamsAndFillStreams(
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception {
String contentType = req.getContentType();
String method = req.getMethod(); // No need to uppercase... HTTP verbs are case sensitive
String method = req.getMethod(); // No need to uppercase... HTTP verbs are case-sensitive
String uri = req.getRequestURI();
boolean isV2 = getHttpSolrCall(req) instanceof V2HttpCall;
boolean isPost = "POST".equals(method);
Expand Down
Loading
Loading