-
Notifications
You must be signed in to change notification settings - Fork 809
Separate core specific Request Writers from node specific "built in" ones. Move core specific to using ImplicitPlugins.json. #4073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2c89423
ec3b79e
509e490
9735afc
ac54910
de08d31
a0a9da4
c7e4693
e4f3370
4a9745d
95b9fea
513ad20
6741fe6
816c92b
f2364d3
45704e1
1ed1885
f88abea
fe51a67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # 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: other | ||
| authors: | ||
| - name: Eric Pugh | ||
| - name: David Smiley | ||
| links: | ||
| - name: PR#4073 | ||
| url: https://github.com/apache/solr/pull/4073 |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -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; | ||||||||
|
|
@@ -112,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; | ||||||||
|
|
@@ -129,19 +126,9 @@ | |||||||
| 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.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.ResponseWritersRegistry; | ||||||||
| 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; | ||||||||
|
|
@@ -3088,51 +3075,6 @@ public PluginBag<QueryResponseWriter> getResponseWriters() { | |||||||
|
|
||||||||
| private final PluginBag<QueryResponseWriter> responseWriters = | ||||||||
| new PluginBag<>(QueryResponseWriter.class, this); | ||||||||
| public static final Map<String, QueryResponseWriter> DEFAULT_RESPONSE_WRITERS; | ||||||||
|
|
||||||||
| static { | ||||||||
| HashMap<String, QueryResponseWriter> 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() { | ||||||||
| 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); | ||||||||
|
|
@@ -3148,11 +3090,48 @@ 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 node/container-level requests. | ||||||||
| * | ||||||||
| * @param writerName the writer name, or null for default | ||||||||
| * @return the response writer, never null | ||||||||
| * @deprecated Use {@link ResponseWritersRegistry#getWriter(String)} instead. | ||||||||
| */ | ||||||||
| @Deprecated | ||||||||
| public static QueryResponseWriter getAdminResponseWriter(String writerName) { | ||||||||
| return ResponseWritersRegistry.getWriter(writerName); | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * 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<String, QueryResponseWriter> defaultWriters = new HashMap<>(); | ||||||||
|
|
||||||||
| // Start with built-in writers that are always available | ||||||||
| defaultWriters.putAll(ResponseWritersRegistry.getAllWriters()); | ||||||||
|
|
||||||||
| // Load writers from ImplicitPlugins.json (may override built-ins) | ||||||||
| List<PluginInfo> implicitWriters = getImplicitResponseWriters(); | ||||||||
| for (PluginInfo info : implicitWriters) { | ||||||||
| try { | ||||||||
| QueryResponseWriter writer = | ||||||||
| createInstance( | ||||||||
| info.className, | ||||||||
| QueryResponseWriter.class, | ||||||||
| "queryResponseWriter", | ||||||||
| null, | ||||||||
| getResourceLoader()); | ||||||||
|
||||||||
| getResourceLoader()); | |
| getResourceLoader()); | |
| initPlugin(info, writer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is probably a "good thing" yet none of our QueryResponseWriters need it, so we don't have it. If this plugin creation was centralized more, then it might happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great feedback from Copilot.
Agreed that we should better normalize/standardize plugin instantiation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the VelocityResponseWriter needs a SolrResourceLoader (from a core). Any way, we need not concern ourselves with that.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| import org.apache.solr.core.CoreContainer; | ||
| import org.apache.solr.core.SolrCore; | ||
| 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; | ||
|
|
@@ -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. */ | ||
|
|
@@ -145,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<String, String> getPathTemplateValues() { | ||
|
|
@@ -195,16 +196,21 @@ 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. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this is saying that the |
||
| * | ||
| * <p>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 | ||
| 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 SolrCore.DEFAULT_RESPONSE_WRITERS.getOrDefault( | ||
| wt, SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard")); | ||
| return ResponseWritersRegistry.getWriter(wt); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /* | ||
| * 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. | ||
| * | ||
| * <p>This writer handles streaming of large files (such as index files) by looking for a {@link | ||
| * 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. | ||
| * | ||
| * <p>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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SolrCore.initWriters() passes
core=nullinto createInstance(...) when loading core-level response writers. If any QueryResponseWriter has a SolrCore constructor (or otherwise expects a non-null core), this will either instantiate with a null core or skip the core-aware constructor path. Usethis(the current SolrCore) instead of null when creating core-specific writers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting, and I think a separte issue. None of the QueryResponseWriter's take a core. RawResponseWriter looks up a core from a specific request, but doesn't take it directly. There are. a few calls to the createInstance and they mostly pass a
nullvalue. The only place I see a core being passed in is the call fromPackagePluginHolder.initNewInstance. I don't know if there is a refactoring to have two createInstances? Or one that you call that doesn't take a core and then calls this one for a null? Probably for another PR. So, based on this analysis, I am going to leave this null. This is just a bit icky.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to have a version of
createInstancethat doesn't take a SolrCore just to make it clearer and use that in the places we callcreateInstanceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a change -- were response writers receiving the core and now, not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was NOT a change. Only in one instance (that of
PackagePluginHolder) do they get a core. I think it's a refactoring opportunity.