This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_10x by this push:
new 255638e7474 SOLR-18351 SolrJ: ContentStream more deprecation and
replacements (#5235)
255638e7474 is described below
commit 255638e7474ee2316c544b0e21350f96b9c213e5
Author: David Smiley <[email protected]>
AuthorDate: Thu Oct 29 15:25:26 2026 -0400
SOLR-18351 SolrJ: ContentStream more deprecation and replacements (#5235)
SolrJ clients now write request content via RequestWriter.ContentWriter;
ContentStreams from
the long-deprecated SolrRequest.getContentStreams and
RequestWriter.getContentStreams are still
honored. Deprecated ContentStreamUpdateRequest in favor of the new
ContentWriterUpdateRequest,
ConfigSetAdminRequest.Upload.setUploadStream in favor of
setUploadContent, and
ClientUtils.toContentStreams. Upload.setUploadFile no longer gunzips .gz
files or guesses a
missing content type, so pass the content type explicitly.
Co-authored-by: Serhiy Bzhezytskyy <[email protected]>
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Nick Shanin <[email protected]>
---
.../SOLR-18351-10x-contentstream-deprecations.yml | 15 ++
solr/core/src/java/org/apache/solr/api/ApiBag.java | 42 ++-
.../core/src/java/org/apache/solr/cli/SolrCLI.java | 9 +-
.../client/solrj/embedded/EmbeddedSolrServer.java | 39 ++-
.../org/apache/solr/handler/SolrConfigHandler.java | 4 +-
.../solr/handler/admin/SecurityConfHandler.java | 3 +-
.../TestEmbeddedContentStreamCompatibility.java | 133 ++++++++++
.../transform/TestSubQueryTransformerDistrib.java | 7 +-
.../apache/solr/update/MaxSizeAutoCommitTest.java | 4 +-
.../processor/TolerantUpdateProcessorTest.java | 8 +-
.../src/test/org/apache/solr/util/TestUtils.java | 3 +-
.../solr/crossdc/common/MirroredSolrRequest.java | 39 ++-
.../common/MirroredSolrRequestSerializer.java | 4 +-
.../handler/MirroringConfigSetsHandlerTest.java | 4 +-
.../examples/SolrCellRefGuideExamplesTest.java | 6 +-
.../indexing-guide/pages/indexing-with-tika.adoc | 4 +-
.../query-guide/examples/JsonRequestApiTest.java | 4 +-
.../client/solrj/jetty/HttpJettySolrClient.java | 141 +++++-----
.../solrj/jetty/HttpJettySolrClientTest.java | 101 ++++++++
.../TestLegacyContentStreamCompatibility.java | 283 +++++++++++++++++++++
.../org/apache/solr/client/solrj/SolrRequest.java | 16 +-
.../solr/client/solrj/impl/HttpJdkSolrClient.java | 22 +-
.../solr/client/solrj/impl/HttpSolrClient.java | 18 +-
.../solrj/request/ConfigSetAdminRequest.java | 64 +++--
.../solrj/request/ContentStreamUpdateRequest.java | 25 +-
.../solrj/request/ContentWriterUpdateRequest.java | 83 ++++++
.../client/solrj/request/JavaBinRequestWriter.java | 4 +-
.../solr/client/solrj/request/RequestWriter.java | 87 ++++++-
.../client/solrj/request/XMLRequestWriter.java | 1 +
.../apache/solr/client/solrj/util/ClientUtils.java | 8 +-
.../apache/solr/common/util/CommandOperation.java | 42 ---
.../solrj/impl/CloudSolrClientCacheTest.java | 7 -
.../solrj/request/TestConfigSetAdminRequest.java | 10 +-
...DirectJsonQueryRequestFacetingEmbeddedTest.java | 4 +-
...ectJsonQueryRequestFacetingIntegrationTest.java | 4 +-
.../JsonQueryRequestFacetingIntegrationTest.java | 4 +-
.../json/JsonQueryRequestIntegrationTest.java | 4 +-
.../src/java/org/apache/solr/util/TestHarness.java | 7 +-
38 files changed, 995 insertions(+), 268 deletions(-)
diff --git a/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml
b/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml
new file mode 100644
index 00000000000..c2325d463c7
--- /dev/null
+++ b/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml
@@ -0,0 +1,15 @@
+title: >
+ SolrJ clients now write request content via RequestWriter.ContentWriter;
ContentStreams from
+ the long-deprecated SolrRequest.getContentStreams and
RequestWriter.getContentStreams are still
+ honored. Deprecated ContentStreamUpdateRequest in favor of the new
ContentWriterUpdateRequest,
+ ConfigSetAdminRequest.Upload.setUploadStream in favor of setUploadContent,
and
+ ClientUtils.toContentStreams. Upload.setUploadFile no longer gunzips .gz
files or guesses a
+ missing content type, so pass the content type explicitly.
+type: deprecated
+authors:
+ - name: Serhiy Bzhezytskyy
+ - name: Nick Shanin
+ - name: David Smiley
+links:
+ - name: SOLR-18351
+ url: https://issues.apache.org/jira/browse/SOLR-18351
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java
b/solr/core/src/java/org/apache/solr/api/ApiBag.java
index 935881369d2..45e02e2a2c9 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -412,11 +412,51 @@ public class ApiBag {
}
}
+ public static List<CommandOperation> readCommands(
+ Iterable<ContentStream> streams, @SuppressWarnings({"rawtypes"})
NamedList resp)
+ throws IOException {
+ return readCommands(streams, resp, Set.of());
+ }
+
+ /**
+ * Read commands from request streams
+ *
+ * @param streams the streams
+ * @param resp solr query response
+ * @param singletonCommands , commands that cannot be repeated
+ * @return parsed list of commands
+ * @throws IOException if there is an error while parsing the stream
+ */
+ @SuppressWarnings({"deprecation", "unchecked"})
+ public static List<CommandOperation> readCommands(
+ Iterable<ContentStream> streams,
+ @SuppressWarnings({"rawtypes"}) NamedList resp,
+ Set<String> singletonCommands)
+ throws IOException {
+ if (streams == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "missing
content stream");
+ }
+ ArrayList<CommandOperation> ops = new ArrayList<>();
+ for (ContentStream stream : streams) {
+ if ("application/javabin".equals(stream.getContentType())) {
+ ops.addAll(CommandOperation.parse(stream.getStream(),
singletonCommands));
+ } else {
+ ops.addAll(CommandOperation.parse(stream.getReader(),
singletonCommands));
+ }
+ }
+ List<Map<String, Object>> errList = CommandOperation.captureErrors(ops);
+ if (!errList.isEmpty()) {
+ resp.add(CommandOperation.ERR_MSGS, errList);
+ return null;
+ }
+ return ops;
+ }
+
public static List<CommandOperation> getCommandOperations(
ContentStream stream, Map<String, JsonSchemaValidator> validators,
boolean validate) {
List<CommandOperation> parsedCommands = null;
try {
- parsedCommands = CommandOperation.readCommands(Set.of(stream), new
NamedList<>());
+ parsedCommands = readCommands(Set.of(stream), new NamedList<>());
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unable to
parse commands", e);
}
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index cf9d9d4b0e7..555b30bf7c8 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -44,8 +44,7 @@ import org.apache.commons.cli.help.TableDefinition;
import org.apache.commons.cli.help.TextHelpAppendable;
import org.apache.solr.client.api.util.SolrVersion;
import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
-import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.common.util.EnvUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SuppressForbidden;
@@ -604,10 +603,8 @@ public class SolrCLI implements CLIO,
java.util.concurrent.Callable<Integer> {
public static NamedList<Object> postJsonToSolr(
SolrClient solrClient, String updatePath, String jsonBody) throws
Exception {
- ContentStreamBase.StringStream contentStream = new
ContentStreamBase.StringStream(jsonBody);
- contentStream.setContentType(JSON_CONTENT_TYPE);
- ContentStreamUpdateRequest req = new
ContentStreamUpdateRequest(updatePath);
- req.addContentStream(contentStream);
+ ContentWriterUpdateRequest req = new
ContentWriterUpdateRequest(updatePath);
+ req.addContentWithType(jsonBody, JSON_CONTENT_TYPE);
return solrClient.request(req);
}
diff --git
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
index e43bbfb5cdc..ddf16103e6e 100644
---
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
+++
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
@@ -309,14 +309,27 @@ public class EmbeddedSolrServer extends SolrClient {
}
/** A list of streams, non-null. */
+ @SuppressWarnings("deprecation")
private List<ContentStream> getContentStreams(SolrRequest<?> request) throws
IOException {
if (request.getMethod() == SolrRequest.METHOD.GET) return List.of();
if (request instanceof ContentStreamUpdateRequest csur) {
- final Collection<ContentStream> cs = csur.getContentStreams();
- if (cs != null) return new ArrayList<>(cs);
+ final Collection<ContentStream> streams = csur.getContentStreams();
+ if (streams != null) return new ArrayList<>(streams);
}
final RequestWriter.ContentWriter contentWriter =
request.getContentWriter(null);
+ if (contentWriter == null) {
+ final Collection<ContentStream> streams = request.getContentStreams();
+ if (streams != null) return new ArrayList<>(streams);
+ }
+
+ if (contentWriter instanceof RequestWriter.MultipartContentWriter
multipartWriter) {
+ List<ContentStream> parts = new ArrayList<>();
+ for (RequestWriter.NamedPart part : multipartWriter.getParts()) {
+ parts.add(bufferedContentStream(part.name, part.writer));
+ }
+ return parts;
+ }
String cType;
final Utils.BAOS baos = new Utils.BAOS();
@@ -349,6 +362,28 @@ public class EmbeddedSolrServer extends SolrClient {
return List.of();
}
+ private static ContentStream bufferedContentStream(
+ String name, RequestWriter.ContentWriter writer) throws IOException {
+ final Utils.BAOS baos = new Utils.BAOS();
+ writer.write(baos);
+ final byte[] buf = baos.toByteArray();
+ return new ContentStreamBase() {
+ {
+ setName(name);
+ }
+
+ @Override
+ public InputStream getStream() throws IOException {
+ return new ByteArrayInputStream(buf);
+ }
+
+ @Override
+ public String getContentType() {
+ return writer.getContentType();
+ }
+ };
+ }
+
private JavaBinCodec createJavaBinCodec(
final StreamingResponseCallback callback,
final JavaBinResponseWriter.Resolver resolver,
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 9e43499fbe3..d44e7fe9c53 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -49,6 +49,7 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrResponse;
@@ -245,8 +246,7 @@ public class SolrConfigHandler extends RequestHandlerBase
}
private void handlePOST() throws IOException {
- List<CommandOperation> ops =
- CommandOperation.readCommands(req.getContentStreams(),
resp.getValues());
+ List<CommandOperation> ops =
ApiBag.readCommands(req.getContentStreams(), resp.getValues());
if (ops == null) return;
try {
for (; ; ) {
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index c330b6f08b6..353e096d294 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -116,8 +116,7 @@ public abstract class SecurityConfHandler extends
RequestHandlerBase
if (req.getContentStreams() == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No
contentStream");
}
- List<CommandOperation> ops =
- CommandOperation.readCommands(req.getContentStreams(),
rsp.getValues());
+ List<CommandOperation> ops = ApiBag.readCommands(req.getContentStreams(),
rsp.getValues());
if (ops == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No
commands");
}
diff --git
a/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
new file mode 100644
index 00000000000..1002edb43d8
--- /dev/null
+++
b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
@@ -0,0 +1,133 @@
+/*
+ * 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.client.solrj.embedded;
+
+import java.util.List;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
+import org.apache.solr.client.solrj.request.SolrQuery;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.util.EmbeddedSolrServerTestRule;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/** Checks that legacy embedded updates retain every stream, including unnamed
strings. */
+@SuppressWarnings("deprecation")
+public class TestEmbeddedContentStreamCompatibility extends SolrTestCase {
+
+ @ClassRule
+ public static final EmbeddedSolrServerTestRule solrTestRule = new
EmbeddedSolrServerTestRule();
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ solrTestRule.startSolr(SolrTestCaseJ4.TEST_HOME());
+ SolrTestCaseJ4.newRandomConfig();
+
solrTestRule.newCollection().withConfigSet(SolrTestCaseJ4.TEST_COLL1_CONF()).create();
+ }
+
+ @Test
+ public void testUnnamedContentStreamsRetainAllDocuments() throws Exception {
+ assertAllDocuments(false);
+ }
+
+ @Test
+ public void testNamedContentStreamsRetainAllDocuments() throws Exception {
+ assertAllDocuments(true);
+ }
+
+ @Test
+ public void testGenericLegacyStreamsRetainAllDocuments() throws Exception {
+ GenericSolrRequest request =
+ new GenericSolrRequest(
+ SolrRequest.METHOD.POST, "/update",
SolrRequest.SolrRequestType.UPDATE) {
+ @Override
+ public List<ContentStream> getContentStreams() {
+ return List.of(
+ new ContentStreamBase.StringStream(document("1"),
"application/xml"),
+ new ContentStreamBase.StringStream(document("2"),
"application/xml"));
+ }
+ };
+ assertAllDocuments(request);
+ }
+
+ @Test
+ public void testMultipartContentWriterTakesPrecedenceOverLegacyStreams()
throws Exception {
+ GenericSolrRequest request =
+ new GenericSolrRequest(
+ SolrRequest.METHOD.POST, "/update",
SolrRequest.SolrRequestType.UPDATE) {
+ @Override
+ public List<ContentStream> getContentStreams() {
+ throw new AssertionError("An explicit writer must take precedence
over legacy streams");
+ }
+ };
+ request.setContentWriter(
+ (RequestWriter.MultipartContentWriter)
+ () ->
+ List.of(
+ new RequestWriter.NamedPart(
+ "first.xml",
+ new RequestWriter.StringPayloadContentWriter(
+ document("1"), "application/xml")),
+ new RequestWriter.NamedPart(
+ "second.xml",
+ new RequestWriter.StringPayloadContentWriter(
+ document("2"), "application/xml"))));
+ assertAllDocuments(request);
+ }
+
+ private void assertAllDocuments(boolean named) throws Exception {
+ ContentStreamUpdateRequest request = new
ContentStreamUpdateRequest("/update");
+ for (String id : List.of("1", "2")) {
+ ContentStreamBase stream =
+ new ContentStreamBase.StringStream(document(id), "application/xml");
+ if (named) {
+ stream.setName(id + ".xml");
+ } else {
+ assertNull(stream.getName());
+ }
+ request.addContentStream(stream);
+ }
+ assertAllDocuments(request);
+ }
+
+ private void assertAllDocuments(SolrRequest<?> request) throws Exception {
+ SolrClient client = solrTestRule.getSolrClient();
+ client.deleteByQuery("*:*");
+ client.request(request);
+ client.commit();
+ SolrQuery query = new SolrQuery("*:*");
+ query.setFields("id");
+ query.setSort("id", SolrQuery.ORDER.asc);
+ SolrDocumentList documents = client.query(query).getResults();
+ assertEquals("Both content streams must be indexed", 2L,
documents.getNumFound());
+ assertEquals(
+ List.of("1", "2"),
+ documents.stream().map(document ->
document.getFieldValue("id")).toList());
+ }
+
+ private static String document(String id) {
+ return "<add><doc><field name=\"id\">" + id + "</field></doc></add>";
+ }
+}
diff --git
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
index 108196031f7..572b081651a 100644
---
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
+++
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
@@ -39,7 +39,7 @@ import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
@@ -48,7 +48,6 @@ import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.Utils;
import org.apache.solr.security.BasicAuthPlugin;
import org.apache.solr.security.RuleBasedAuthorizationPlugin;
@@ -413,8 +412,8 @@ public class TestSubQueryTransformerDistrib extends
SolrCloudTestCase {
}
upd.append("</update>");
- ContentStreamUpdateRequest req = withBasicAuth(new
ContentStreamUpdateRequest("/update"));
- req.addContentStream(new
ContentStreamBase.StringStream(upd.toString(), "text/xml"));
+ ContentWriterUpdateRequest req = withBasicAuth(new
ContentWriterUpdateRequest("/update"));
+ req.addContentWithType(upd.toString(), "text/xml");
cluster.getSolrClient().request(req, collection);
upd.setLength("<update>".length());
}
diff --git
a/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
b/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
index eb4f6d90e13..3736b61e999 100644
--- a/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
+++ b/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
@@ -26,9 +26,9 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrEventListener;
import org.apache.solr.handler.UpdateRequestHandler;
@@ -252,7 +252,7 @@ public class MaxSizeAutoCommitTest extends SolrTestCaseJ4 {
private List<ContentStream> toContentStreams(List<String> strs) {
ArrayList<ContentStream> streams = new ArrayList<>();
for (String str : strs) {
- streams.addAll(ClientUtils.toContentStreams(str, "text/xml"));
+ streams.add(new ContentStreamBase.StringStream(str, "text/xml"));
}
return streams;
}
diff --git
a/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
b/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
index 421f2885881..f87d1b2d260 100644
---
a/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
+++
b/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
@@ -27,14 +27,13 @@ import java.util.List;
import java.util.Set;
import javax.xml.xpath.XPathExpressionException;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.InputStreamResponseParser;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
@@ -395,9 +394,8 @@ public class TolerantUpdateProcessorTest extends
UpdateProcessorTestBase {
public String update(String chain, String xml) {
try {
- // Use ContentStreamUpdateRequest to send raw XML through
EmbeddedSolrServer
- ContentStreamUpdateRequest xmlRequest = new
ContentStreamUpdateRequest("/update");
- xmlRequest.addContentStream(new ContentStreamBase.StringStream(xml,
"text/xml"));
+ ContentWriterUpdateRequest xmlRequest = new
ContentWriterUpdateRequest("/update");
+ xmlRequest.addContentWithType(xml, "text/xml");
// Set the update chain parameter and request XML response
xmlRequest.getParams().add("update.chain", chain);
diff --git a/solr/core/src/test/org/apache/solr/util/TestUtils.java
b/solr/core/src/test/org/apache/solr/util/TestUtils.java
index d0da76dc419..38a188a4d63 100644
--- a/solr/core/src/test/org/apache/solr/util/TestUtils.java
+++ b/solr/core/src/test/org/apache/solr/util/TestUtils.java
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.ApiBag;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.ContentStream;
@@ -138,7 +139,7 @@ public class TestUtils extends SolrTestCaseJ4 {
ContentStream stream =
new ContentStreamBase.ByteArrayStream(baos.toByteArray(), null,
"application/javabin");
List<CommandOperation> commands =
- CommandOperation.readCommands(List.of(stream), new NamedList<>(),
Set.of("single"));
+ ApiBag.readCommands(List.of(stream), new NamedList<>(),
Set.of("single"));
assertEquals(5, commands.size());
}
diff --git
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
index d30e64aae60..bcc789538d3 100644
---
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
+++
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
@@ -20,13 +20,16 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
+import java.util.stream.Collectors;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.ConfigSetAdminRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.ConfigSetAdminResponse;
@@ -111,11 +114,43 @@ public class MirroredSolrRequest<T extends SolrResponse> {
this.params = params;
}
- @Override
- public Collection<ContentStream> getContentStreams() {
+ /** The raw streams, for serializers that need the individual
name/type/bytes directly. */
+ public Collection<ContentStream> getRawContentStreams() {
return contentStreams;
}
+ @Override
+ public RequestWriter.ContentWriter getContentWriter(String expectedType) {
+ if (contentStreams == null || contentStreams.isEmpty()) return null;
+ if (contentStreams.size() == 1) {
+ return streamWriter(contentStreams.iterator().next());
+ }
+ return new RequestWriter.MultipartContentWriter() {
+ @Override
+ public java.util.List<RequestWriter.NamedPart> getParts() {
+ return contentStreams.stream()
+ .map(cs -> new RequestWriter.NamedPart(cs.getName(),
streamWriter(cs)))
+ .collect(Collectors.toList());
+ }
+ };
+ }
+
+ private static RequestWriter.ContentWriter streamWriter(ContentStream
stream) {
+ return new RequestWriter.ContentWriter() {
+ @Override
+ public void write(OutputStream os) throws IOException {
+ try (var inStream = stream.getStream()) {
+ inStream.transferTo(os);
+ }
+ }
+
+ @Override
+ public String getContentType() {
+ return stream.getContentType();
+ }
+ };
+ }
+
@Override
protected ConfigSetAdminResponse createResponse(NamedList<Object>
namedList) {
return new ConfigSetAdminResponse();
diff --git
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
index 137d46f106e..d9ae0e961bb 100644
---
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
+++
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
@@ -225,9 +225,9 @@ public class MirroredSolrRequestSerializer
}
} else if (solrRequest instanceof
MirroredSolrRequest.MirroredConfigSetRequest config) {
map.put("method", config.getMethod().toString());
- if (config.getContentStreams() != null) {
+ if (config.getRawContentStreams() != null) {
List<Map<String, Object>> streamsList = new ArrayList<>();
- for (ContentStream cs : config.getContentStreams()) {
+ for (ContentStream cs : config.getRawContentStreams()) {
Map<String, Object> streamMap = new HashMap<>();
streamMap.put("name", cs.getName());
streamMap.put("contentType", cs.getContentType());
diff --git
a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
index b115f4cdcdb..84526c1dad5 100644
---
a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
+++
b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
@@ -153,7 +153,9 @@ public class MirroringConfigSetsHandlerTest extends
SolrTestCaseJ4 {
if (expectStreams) {
List<ContentStream> sourceStreams = (List<ContentStream>)
req.getContentStreams();
assertNotNull("source streams missing", sourceStreams);
- List<ContentStream> mirroredStreams = (List<ContentStream>)
solrRequest.getContentStreams();
+ List<ContentStream> mirroredStreams =
+ (List<ContentStream>)
+ ((MirroredSolrRequest.MirroredConfigSetRequest)
solrRequest).getRawContentStreams();
assertNotNull("mirrored streams missing", mirroredStreams);
assertEquals("number of streams", sourceStreams.size(),
mirroredStreams.size());
for (int i = 0; i < sourceStreams.size(); i++) {
diff --git
a/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
b/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
index 14d6b40de29..33314a357da 100644
---
a/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
+++
b/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
@@ -23,7 +23,7 @@ import static
org.apache.solr.client.ref_guide_examples.ExpectedOutputVerifier.p
import java.nio.file.Path;
import java.util.List;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
@@ -71,8 +71,8 @@ public class SolrCellRefGuideExamplesTest extends
SolrCloudTestCase {
List.of("Error loading class
'solr.extraction.ExtractingRequestHandler'"),
() -> {
// tag::solr-cell-pdf-extraction[]
- ContentStreamUpdateRequest extractRequest =
- new ContentStreamUpdateRequest("/update/extract");
+ ContentWriterUpdateRequest extractRequest =
+ new ContentWriterUpdateRequest("/update/extract");
extractRequest.addFile(Path.of(PDF_DOC_PATH), "application/pdf");
extractRequest.setParam("extractOnly", "true");
NamedList<Object> extractResponse =
diff --git
a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
index d17a899b7ab..28d7df9431a 100644
--- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
+++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
@@ -677,8 +677,8 @@
include::example$SolrCellRefGuideExamplesTest.java[tag=solr-cell-pdf-extraction]
This operation streams the file `my-file.pdf` into the Solr index for
`my_collection`.
The sample code above calls the extract command, but you can substitute other
commands that are supported by Solr Cell.
-The key class to use is the `ContentStreamUpdateRequest`, which makes sure the
ContentStreams are set properly.
+The key class to use is the `ContentWriterUpdateRequest`, which makes sure the
request content is written properly.
SolrJ takes care of the rest.
-Note that the `ContentStreamUpdateRequest` is not just specific to Solr Cell.
+Note that the `ContentWriterUpdateRequest` is not just specific to Solr Cell.
You can send CSV to the CSV Update handler and to any other Request Handler
that works with Content Streams for updates.
diff --git
a/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
b/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
index 16f60ea2dad..a5dcc194263 100644
--- a/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
+++ b/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
@@ -26,7 +26,7 @@ import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.request.SolrQuery;
import org.apache.solr.client.solrj.request.json.DomainMap;
import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
@@ -61,7 +61,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
.process(cluster.getSolrClient());
- ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
up.setParam("collection", COLLECTION_NAME);
up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git
a/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
b/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
index f4e5307bada..2f5cb96fcbf 100644
---
a/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
+++
b/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.net.ConnectException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,7 +54,6 @@ import org.apache.solr.client.solrj.response.ResponseParser;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.EnvUtils;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
@@ -65,7 +63,6 @@ import org.eclipse.jetty.client.AuthenticationStore;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpProxy;
-import org.eclipse.jetty.client.InputStreamRequestContent;
import org.eclipse.jetty.client.InputStreamResponseListener;
import org.eclipse.jetty.client.MultiPartRequestContent;
import org.eclipse.jetty.client.Origin.Address;
@@ -710,10 +707,14 @@ public class HttpJettySolrClient extends HttpSolrClient {
}
}
+ private record PartContent(
+ RequestWriter.ContentWriter writer, OutputStreamRequestContent content)
{}
+
private static class MakeRequestReturnValue {
final Request request;
final RequestWriter.ContentWriter contentWriter;
final OutputStreamRequestContent requestContent;
+ final List<PartContent> partContents;
MakeRequestReturnValue(
Request request,
@@ -722,12 +723,21 @@ public class HttpJettySolrClient extends HttpSolrClient {
this.request = request;
this.contentWriter = contentWriter;
this.requestContent = requestContent;
+ this.partContents = null;
+ }
+
+ MakeRequestReturnValue(Request request, List<PartContent> partContents) {
+ this.request = request;
+ this.contentWriter = null;
+ this.requestContent = null;
+ this.partContents = partContents;
}
MakeRequestReturnValue(Request request) {
this.request = request;
this.contentWriter = null;
this.requestContent = null;
+ this.partContents = null;
}
}
@@ -751,37 +761,33 @@ public class HttpJettySolrClient extends HttpSolrClient {
if (SolrRequest.METHOD.POST == solrRequest.getMethod()
|| SolrRequest.METHOD.PUT == solrRequest.getMethod()) {
- RequestWriter.ContentWriter contentWriter =
requestWriter.getContentWriter(solrRequest);
- Collection<ContentStream> streams =
- contentWriter == null ? requestWriter.getContentStreams(solrRequest)
: null;
-
- boolean isMultipart = isMultipart(streams);
+ RequestWriter.ContentWriter contentWriter =
+ requestWriter.getContentWriterOrStreams(solrRequest);
HttpMethod method =
SolrRequest.METHOD.POST == solrRequest.getMethod() ? HttpMethod.POST
: HttpMethod.PUT;
- if (contentWriter != null) {
+ if (contentWriter instanceof RequestWriter.MultipartContentWriter
multipartWriter) {
+ // send server list and request list as query string params
+ ModifiableSolrParams queryParams =
calculateQueryParams(this.urlParamNames, wparams);
+ queryParams.add(calculateQueryParams(solrRequest.getQueryParams(),
wparams));
+ Request req = httpClient.newRequest(url +
queryParams.toQueryString()).method(method);
+ var r = fillMultipartContent(req, multipartWriter, wparams);
+ decorateRequest(r.request, solrRequest, isAsync);
+ return r;
+
+ } else if (contentWriter != null) {
var content = new
OutputStreamRequestContent(contentWriter.getContentType());
var r = httpClient.newRequest(url +
wparams.toQueryString()).method(method).body(content);
decorateRequest(r, solrRequest, isAsync);
return new MakeRequestReturnValue(r, contentWriter, content);
- } else if (streams == null || isMultipart) {
- // send server list and request list as query string params
+ } else {
+ // application/x-www-form-urlencoded, params only
ModifiableSolrParams queryParams =
calculateQueryParams(this.urlParamNames, wparams);
queryParams.add(calculateQueryParams(solrRequest.getQueryParams(),
wparams));
Request req = httpClient.newRequest(url +
queryParams.toQueryString()).method(method);
- var r = fillContentStream(req, streams, wparams, isMultipart);
- decorateRequest(r, solrRequest, isAsync);
- return new MakeRequestReturnValue(r);
-
- } else {
- // If it has one stream, it is the post body, put the params in the URL
- ContentStream contentStream = streams.iterator().next();
- var content =
- new InputStreamRequestContent(
- contentStream.getContentType(), contentStream.getStream());
- var r = httpClient.newRequest(url +
wparams.toQueryString()).method(method).body(content);
+ var r = fillParamsOnlyBody(req, wparams);
decorateRequest(r, solrRequest, isAsync);
return new MakeRequestReturnValue(r);
}
@@ -798,63 +804,62 @@ public class HttpJettySolrClient extends HttpSolrClient {
try (var output = mrrv.requestContent.getOutputStream()) {
mrrv.contentWriter.write(output);
}
+ } else if (mrrv.partContents != null) {
+ for (PartContent part : mrrv.partContents) {
+ try (var output = part.content().getOutputStream()) {
+ part.writer().write(output);
+ }
+ }
}
return mrrv.request;
}
- private Request fillContentStream(
+ private Request fillParamsOnlyBody(Request req, ModifiableSolrParams
wparams) {
+ String queryString = wparams.toQueryString();
+ // remove the leading "?" if there is any
+ queryString = queryString.startsWith("?") ? queryString.substring(1) :
queryString;
+ req.body(
+ new StringRequestContent(
+ "application/x-www-form-urlencoded", queryString,
FALLBACK_CHARSET));
+ return req;
+ }
+
+ private MakeRequestReturnValue fillMultipartContent(
Request req,
- Collection<ContentStream> streams,
- ModifiableSolrParams wparams,
- boolean isMultipart)
+ RequestWriter.MultipartContentWriter multipartWriter,
+ ModifiableSolrParams wparams)
throws IOException {
- if (isMultipart) {
- // multipart/form-data
- try (MultiPartRequestContent content = new MultiPartRequestContent()) {
- Iterator<String> iter = wparams.getParameterNamesIterator();
- while (iter.hasNext()) {
- String key = iter.next();
- String[] vals = wparams.getParams(key);
- if (vals != null) {
- for (String val : vals) {
- content.addPart(
- new MultiPart.ContentSourcePart(key, null, null, new
StringRequestContent(val)));
- }
- }
- }
- if (streams != null) {
- for (ContentStream contentStream : streams) {
- String contentType = contentStream.getContentType();
- if (contentType == null) {
- contentType = "multipart/form-data"; // default
- }
- String name = contentStream.getName();
- if (name == null) {
- name = "";
- }
- HttpFields.Mutable fields = HttpFields.build(1);
- fields.add(HttpHeader.CONTENT_TYPE, contentType);
+ MultiPartRequestContent content = new MultiPartRequestContent();
+ try {
+ Iterator<String> iter = wparams.getParameterNamesIterator();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ String[] vals = wparams.getParams(key);
+ if (vals != null) {
+ for (String val : vals) {
content.addPart(
- new MultiPart.ContentSourcePart(
- name,
- contentStream.getName(),
- fields,
- new InputStreamRequestContent(contentStream.getStream())));
+ new MultiPart.ContentSourcePart(key, null, null, new
StringRequestContent(val)));
}
}
- req.body(content);
}
- } else {
- // application/x-www-form-urlencoded
- String queryString = wparams.toQueryString();
- // remove the leading "?" if there is any
- queryString = queryString.startsWith("?") ? queryString.substring(1) :
queryString;
- req.body(
- new StringRequestContent(
- "application/x-www-form-urlencoded", queryString,
FALLBACK_CHARSET));
+ List<PartContent> partContents = new ArrayList<>();
+ for (RequestWriter.NamedPart part : multipartWriter.getParts()) {
+ String contentType = part.writer.getContentType();
+ if (contentType == null) {
+ contentType = "multipart/form-data"; // default
+ }
+ String name = part.name == null ? "" : part.name;
+ HttpFields.Mutable fields = HttpFields.build(1);
+ fields.add(HttpHeader.CONTENT_TYPE, contentType);
+ var partContent = new OutputStreamRequestContent(contentType);
+ content.addPart(new MultiPart.ContentSourcePart(name, part.name,
fields, partContent));
+ partContents.add(new PartContent(part.writer, partContent));
+ }
+ req.body(content);
+ return new MakeRequestReturnValue(req, partContents);
+ } finally {
+ content.close();
}
-
- return req;
}
@Override
diff --git
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
index a47e981be5b..f621307ce58 100644
---
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
+++
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
@@ -22,8 +22,11 @@ import static
org.hamcrest.core.StringContains.containsStringIgnoringCase;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
+import java.util.Collection;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -40,8 +43,12 @@ import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClientTestBase;
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
import org.apache.solr.client.solrj.request.SolrPing;
import org.apache.solr.client.solrj.request.SolrQuery;
import org.apache.solr.client.solrj.request.XMLRequestWriter;
@@ -53,6 +60,8 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SolrNamedThreadFactory;
@@ -99,6 +108,98 @@ public class HttpJettySolrClientTest extends
HttpSolrClientTestBase {
}
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testRequestContentStreams() throws Exception {
+ var req =
+ new AbstractUpdateRequest(SolrRequest.METHOD.POST, "/update") {
+ @Override
+ public Collection<ContentStream> getContentStreams() {
+ return legacyStreams();
+ }
+ };
+ assertLegacyContentSent(new JavaBinRequestWriter(), req);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testRequestWriterContentStreams() throws Exception {
+ var writer =
+ new RequestWriter() {
+ @Override
+ public ContentWriter getContentWriter(SolrRequest<?> req) {
+ return null;
+ }
+
+ @Override
+ public Collection<ContentStream> getContentStreams(SolrRequest<?>
req) {
+ return legacyStreams();
+ }
+
+ @Override
+ public void write(SolrRequest<?> request, OutputStream os) {}
+
+ @Override
+ public String getUpdateContentType() {
+ return "text/plain";
+ }
+ };
+ assertLegacyContentSent(writer, new
GenericSolrRequest(SolrRequest.METHOD.POST, "/update"));
+ }
+
+ @SuppressWarnings("deprecation")
+ private static Collection<ContentStream> legacyStreams() {
+ var stream = new ContentStreamBase.StringStream("legacy content");
+ stream.setContentType("text/plain");
+ return List.of(stream);
+ }
+
+ private void assertLegacyContentSent(RequestWriter writer, SolrRequest<?>
req) throws Exception {
+ String url = solrTestRule.getBaseUrl() + DEBUG_SERVLET_PATH;
+ try (var client =
+ new HttpJettySolrClient.Builder(url)
+ .withDefaultCollection(DEFAULT_COLLECTION)
+ .withRequestWriter(writer)
+ .build()) {
+ try {
+ client.request(req);
+ } catch (RemoteSolrException ignored) {
+ }
+ assertEquals("legacy content", new String(DebugServlet.requestBody,
StandardCharsets.UTF_8));
+ } finally {
+ DebugServlet.clear();
+ }
+ }
+
+ @Test
+ public void testMultipartUpload() throws Exception {
+ ContentWriterUpdateRequest req = new ContentWriterUpdateRequest("/update");
+ req.addPart(
+ "firstPart", new RequestWriter.StringPayloadContentWriter("first
content", "text/plain"));
+ req.addPart(
+ "secondPart", new RequestWriter.StringPayloadContentWriter("second
content", "text/plain"));
+ req.setParam("someParam", "someValue");
+
+ String url = solrTestRule.getBaseUrl() + DEBUG_SERVLET_PATH;
+ try (var client =
+ new
HttpJettySolrClient.Builder(url).withDefaultCollection(DEFAULT_COLLECTION).build())
{
+ try {
+ client.request(req);
+ } catch (RemoteSolrException ignored) {
+ }
+
+ String contentType = DebugServlet.headers.get("content-type");
+ assertTrue(contentType, contentType != null &&
contentType.startsWith("multipart/form-data"));
+ String body = new String(DebugServlet.requestBody,
StandardCharsets.UTF_8);
+ assertTrue(body, body.contains("firstPart"));
+ assertTrue(body, body.contains("first content"));
+ assertTrue(body, body.contains("secondPart"));
+ assertTrue(body, body.contains("second content"));
+ } finally {
+ DebugServlet.clear();
+ }
+ }
+
@Test
public void test0IdleTimeout() throws Exception {
SolrQuery q = new SolrQuery("*:*");
diff --git
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
new file mode 100644
index 00000000000..4afa8061f83
--- /dev/null
+++
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
@@ -0,0 +1,283 @@
+/*
+ * 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.client.solrj.jetty;
+
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
+import org.apache.solr.client.solrj.request.RequestWriter;
+import org.apache.solr.client.solrj.request.XMLRequestWriter;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
+import org.apache.solr.client.solrj.response.XMLResponseParser;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.embedded.JettyConfig;
+import org.apache.solr.util.SolrJettyTestRule;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/** Checks legacy request bodies and custom stream writers through both HTTP
clients. */
+@SuppressWarnings("deprecation")
+public class TestLegacyContentStreamCompatibility extends SolrTestCase {
+
+ private static final String REQUEST_BODY = "original-request-body";
+ private static final String WRITER_BODY = "custom-writer-body";
+ private static final String CONTENT_TYPE = "text/plain";
+ private static final String FORM_CONTENT_TYPE =
"application/x-www-form-urlencoded";
+ private static final AtomicReference<CapturedRequest> capturedRequest = new
AtomicReference<>();
+
+ @ClassRule public static final SolrJettyTestRule solrTestRule = new
SolrJettyTestRule();
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ JettyConfig config =
+ JettyConfig.builder()
+ .withServlet(new ServletHolder(RawBodyServlet.class),
"/legacy-content/*")
+ .build();
+ solrTestRule.startSolr(createTempDir(), new Properties(), config);
+ }
+
+ @After
+ public void clearServlet() {
+ capturedRequest.set(null);
+ }
+
+ @Test
+ public void testJettyLegacyRequestWithJavaBinWriter() throws Exception {
+ assertLegacyRequestBody(false, new JavaBinRequestWriter());
+ }
+
+ @Test
+ public void testJettyLegacyRequestWithXmlWriter() throws Exception {
+ assertLegacyRequestBody(false, new XMLRequestWriter());
+ }
+
+ @Test
+ public void testJdkLegacyRequestWithJavaBinWriter() throws Exception {
+ assertLegacyRequestBody(true, new JavaBinRequestWriter());
+ }
+
+ @Test
+ public void testJdkLegacyRequestWithXmlWriter() throws Exception {
+ assertLegacyRequestBody(true, new XMLRequestWriter());
+ }
+
+ @Test
+ public void testJettyJavaBinWriterStreamOverride() throws Exception {
+ assertWriterOverride(false, false);
+ }
+
+ @Test
+ public void testJettyXmlWriterStreamOverride() throws Exception {
+ assertWriterOverride(false, true);
+ }
+
+ @Test
+ public void testJdkJavaBinWriterStreamOverride() throws Exception {
+ assertWriterOverride(true, false);
+ }
+
+ @Test
+ public void testJdkXmlWriterStreamOverride() throws Exception {
+ assertWriterOverride(true, true);
+ }
+
+ @Test
+ public void testJettyContentWriterControl() throws Exception {
+ assertExplicitContentWriter(false, CONTENT_TYPE);
+ }
+
+ @Test
+ public void testJdkContentWriterControl() throws Exception {
+ assertExplicitContentWriter(true, CONTENT_TYPE);
+ }
+
+ @Test
+ public void testJettyFormContentWriterControl() throws Exception {
+ assertExplicitContentWriter(false, FORM_CONTENT_TYPE);
+ }
+
+ @Test
+ public void testJdkFormContentWriterControl() throws Exception {
+ assertExplicitContentWriter(true, FORM_CONTENT_TYPE);
+ }
+
+ private void assertLegacyRequestBody(boolean jdk, RequestWriter writer)
throws Exception {
+ GenericSolrRequest request =
+ new GenericSolrRequest(
+ SolrRequest.METHOD.POST, "/update",
SolrRequest.SolrRequestType.UPDATE) {
+ @Override
+ public Collection<ContentStream> getContentStreams() {
+ return streams(REQUEST_BODY);
+ }
+ };
+ assertNull(request.getContentWriter(null));
+ assertPostBody(jdk, writer, request, REQUEST_BODY);
+ }
+
+ private void assertWriterOverride(boolean jdk, boolean xml) throws Exception
{
+ AtomicInteger calls = new AtomicInteger();
+ RequestWriter writer = streamOverrideWriter(xml, calls);
+ assertPostBody(jdk, writer, new LegacyStreamsRequest(), WRITER_BODY);
+ assertTrue("The custom writer's stream hook must be consulted",
calls.get() > 0);
+ }
+
+ private RequestWriter streamOverrideWriter(boolean xml, AtomicInteger calls)
{
+ RequestWriter writer;
+ if (xml) {
+ writer =
+ new XMLRequestWriter() {
+ @Override
+ public Collection<ContentStream> getContentStreams(SolrRequest<?>
request) {
+ calls.incrementAndGet();
+ return streams(WRITER_BODY);
+ }
+ };
+ } else {
+ writer =
+ new JavaBinRequestWriter() {
+ @Override
+ public Collection<ContentStream> getContentStreams(SolrRequest<?>
request) {
+ calls.incrementAndGet();
+ return streams(WRITER_BODY);
+ }
+ };
+ }
+ return writer;
+ }
+
+ private void assertExplicitContentWriter(boolean jdk, String contentType)
throws Exception {
+ GenericSolrRequest request =
+ new GenericSolrRequest(
+ SolrRequest.METHOD.POST, "/update",
SolrRequest.SolrRequestType.UPDATE);
+ request.setContentWriter(
+ new RequestWriter.StringPayloadContentWriter(REQUEST_BODY,
contentType));
+ for (boolean xml : List.of(false, true)) {
+ AtomicInteger calls = new AtomicInteger();
+ CapturedRequest captured =
+ assertPostBody(jdk, streamOverrideWriter(xml, calls), request,
REQUEST_BODY);
+ assertEquals(contentType, captured.contentType());
+ assertEquals("An explicit writer must take precedence over the stream
hook", 0, calls.get());
+ }
+ }
+
+ private CapturedRequest assertPostBody(
+ boolean jdk, RequestWriter writer, SolrRequest<?> request, String
expectedBody)
+ throws Exception {
+ capturedRequest.set(null);
+ String url = solrTestRule.getBaseUrl() + "/legacy-content";
+ try (SolrClient client =
+ jdk
+ ? new HttpJdkSolrClient.Builder(url)
+ .withDefaultCollection("collection1")
+ .withRequestWriter(writer)
+ .withResponseParser(new XMLResponseParser())
+ .build()
+ : new HttpJettySolrClient.Builder(url)
+ .withDefaultCollection("collection1")
+ .withRequestWriter(writer)
+ .withResponseParser(new XMLResponseParser())
+ .build()) {
+ client.request(request);
+ CapturedRequest captured = capturedRequest.get();
+ assertNotNull("The servlet must capture the POST body", captured);
+ assertEquals("POST", captured.method());
+ // Record the legacy content type separately from the request-body
assertion.
+ System.out.println(
+ "Captured POST: client="
+ + client.getClass().getSimpleName()
+ + ", writer="
+ + writer.getClass().getName()
+ + ", contentType="
+ + captured.contentType()
+ + ", body="
+ + new String(captured.body(), StandardCharsets.UTF_8));
+ assertArrayEquals(expectedBody.getBytes(StandardCharsets.UTF_8),
captured.body());
+ return captured;
+ }
+ }
+
+ private record CapturedRequest(String method, String contentType, byte[]
body) {}
+
+ /** Captures bytes before any servlet parameter parsing can consume a
form-encoded body. */
+ public static class RawBodyServlet extends HttpServlet {
+
+ @Override
+ protected void doHead(HttpServletRequest request, HttpServletResponse
response) {
+ response.setContentType("application/xml; charset=UTF-8");
+ response.setStatus(HttpServletResponse.SC_OK);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse
response)
+ throws IOException {
+ byte[] body = request.getInputStream().readAllBytes();
+ capturedRequest.set(new CapturedRequest(request.getMethod(),
request.getContentType(), body));
+ response.setContentType("application/xml; charset=UTF-8");
+ response
+ .getOutputStream()
+ .write(
+ "<response><lst name=\"responseHeader\"><int
name=\"status\">0</int></lst></response>"
+ .getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ private static List<ContentStream> streams(String body) {
+ return List.of(new ContentStreamBase.StringStream(body, CONTENT_TYPE));
+ }
+
+ private static class LegacyStreamsRequest extends
SolrRequest<SimpleSolrResponse> {
+
+ LegacyStreamsRequest() {
+ super(METHOD.POST, "/update", SolrRequestType.UPDATE);
+ }
+
+ @Override
+ public Collection<ContentStream> getContentStreams() {
+ return streams(REQUEST_BODY);
+ }
+
+ @Override
+ public SolrParams getParams() {
+ return new ModifiableSolrParams();
+ }
+
+ @Override
+ protected SimpleSolrResponse createResponse(NamedList<Object> response) {
+ return new SimpleSolrResponse();
+ }
+ }
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
index 5dc69902c0a..9ec9540894c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
@@ -272,14 +272,6 @@ public abstract class SolrRequest<T> implements
Serializable {
return ApiVersion.V1;
}
- /**
- * @deprecated Please use {@link SolrRequest#getContentWriter(String)}
instead.
- */
- @Deprecated(since = "7.2")
- public Collection<ContentStream> getContentStreams() throws IOException {
- return null;
- }
-
/**
* If a request object wants to do a push write, implement this method.
*
@@ -290,6 +282,14 @@ public abstract class SolrRequest<T> implements
Serializable {
return null;
}
+ /**
+ * @deprecated Please use {@link SolrRequest#getContentWriter(String)}
instead.
+ */
+ @Deprecated(since = "7.2")
+ public Collection<ContentStream> getContentStreams() throws IOException {
+ return null;
+ }
+
/**
* Create a new SolrResponse to hold the response from the server. If the
response extends {@link
* SolrResponse}, then there's no need to use the arguments, as {@link
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index 722b241cef6..7e328150119 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -34,7 +34,6 @@ import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
-import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -54,7 +53,6 @@ import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.ObjectReleaseTracker;
@@ -293,14 +291,8 @@ public class HttpJdkSolrClient extends HttpSolrClient {
ModifiableSolrParams queryParams)
throws IOException, URISyntaxException {
- final RequestWriter.ContentWriter contentWriter =
requestWriter.getContentWriter(solrRequest);
-
- final Collection<ContentStream> streams;
- if (contentWriter == null) {
- streams = requestWriter.getContentStreams(solrRequest);
- } else {
- streams = null;
- }
+ final RequestWriter.ContentWriter contentWriter =
+ requestWriter.getContentWriterOrStreams(solrRequest);
String contentType = "application/x-www-form-urlencoded";
if (contentWriter != null && contentWriter.getContentType() != null) {
@@ -308,7 +300,7 @@ public class HttpJdkSolrClient extends HttpSolrClient {
}
reqb.header("Content-Type", contentType);
- if (isMultipart(streams)) {
+ if (isMultipart(contentWriter)) {
throw new UnsupportedOperationException("This client does not support
multipart.");
}
@@ -323,14 +315,6 @@ public class HttpJdkSolrClient extends HttpSolrClient {
bodyPublisher =
HttpRequest.BodyPublishers.ofInputStream(
() -> pReq.beginContentWriting(contentWriter,
this.requestBodyExecutor));
- } else if (streams != null && streams.size() == 1) {
- boolean success = maybeTryHeadRequest(url);
- if (!success) {
- reqb.version(HttpClient.Version.HTTP_1_1);
- }
-
- InputStream is = streams.iterator().next().getStream();
- bodyPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> is);
} else {
// move any params specified in urlParamNames or solrRequest from
queryParams into urlParams
ModifiableSolrParams urlParams = calculateQueryParams(urlParamNames,
queryParams);
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
index 4f8918f42cb..fabd2c365ed 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
@@ -53,7 +53,6 @@ import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -164,14 +163,8 @@ public abstract class HttpSolrClient extends SolrClient {
return params;
}
- protected boolean isMultipart(Collection<ContentStream> streams) {
- boolean isMultipart = false;
- if (streams != null) {
- boolean hasNullStreamName = false;
- hasNullStreamName = streams.stream().anyMatch(cs -> cs.getName() ==
null);
- isMultipart = !hasNullStreamName && streams.size() > 1;
- }
- return isMultipart;
+ protected boolean isMultipart(RequestWriter.ContentWriter contentWriter) {
+ return contentWriter instanceof RequestWriter.MultipartContentWriter;
}
protected ModifiableSolrParams calculateQueryParams(
@@ -192,10 +185,9 @@ public abstract class HttpSolrClient extends SolrClient {
}
protected void validateGetRequest(SolrRequest<?> solrRequest) throws
IOException {
- RequestWriter.ContentWriter contentWriter =
requestWriter.getContentWriter(solrRequest);
- Collection<ContentStream> streams =
- contentWriter == null ? requestWriter.getContentStreams(solrRequest) :
null;
- if (contentWriter != null || streams != null) {
+ RequestWriter.ContentWriter contentWriter =
+ requestWriter.getContentWriterOrStreams(solrRequest);
+ if (contentWriter != null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "GET can't
send streams!");
}
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
index 2154f63ba12..c70a65510dc 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
@@ -20,6 +20,7 @@ import static org.apache.solr.common.params.CommonParams.NAME;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
@@ -31,7 +32,6 @@ import
org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
-import org.apache.solr.common.util.ContentStreamBase.FileStream;
import org.apache.solr.common.util.NamedList;
/**
@@ -140,9 +140,15 @@ public abstract class ConfigSetAdminRequest<
* file to upload if {@link #setFilePath} is being used.
*/
public static class Upload extends ConfigSetSpecificAdminRequest<Upload> {
- private static final String NO_STREAM_ERROR = "There must be a
ContentStream or File to Upload";
+ private static final String NO_STREAM_ERROR = "There must be content or a
File to Upload";
+ /**
+ * @deprecated Use {@link #setUploadContent}; this is only set by {@link
#setUploadStream}.
+ */
+ @Deprecated(since = "10.1")
protected ContentStream stream;
+
+ protected RequestWriter.ContentWriter contentWriter;
protected String filePath;
protected Boolean overwrite;
@@ -180,13 +186,22 @@ public abstract class ConfigSetAdminRequest<
* <p>This should either be a ZIP file containing the entire configset
being uploaded, or an
* individual file to upload into an existing configset if {@link
#setFilePath} is being used.
*
- * @see #setUploadStream
+ * @see #setUploadContent
*/
public final Upload setUploadFile(final Path file, final String
contentType)
throws IOException {
- final FileStream fileStream = new FileStream(file);
- fileStream.setContentType(contentType);
- return setUploadStream(fileStream);
+ return setUploadContent(
+ new RequestWriter.ContentWriter() {
+ @Override
+ public void write(OutputStream os) throws IOException {
+ Files.copy(file, os);
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+ });
}
/**
@@ -220,48 +235,51 @@ public abstract class ConfigSetAdminRequest<
}
/**
- * Specify the ContentStream to upload.
+ * Specify the content to upload.
*
* <p>This should either be a ZIP file containing the entire configset
being uploaded, or an
* individual file to upload into an existing configset if {@link
#setFilePath} is being used.
*
- * @see #setUploadStream
+ * @see #setUploadFile
*/
+ public final Upload setUploadContent(final RequestWriter.ContentWriter
contentWriter) {
+ this.contentWriter = contentWriter;
+ return getThis();
+ }
+
+ /**
+ * Specify the ContentStream to upload.
+ *
+ * @deprecated Use {@link #setUploadContent}.
+ */
+ @Deprecated(since = "10.1")
public final Upload setUploadStream(final ContentStream stream) {
this.stream = stream;
return getThis();
}
@Override
+ @Deprecated(since = "10.1")
public Collection<ContentStream> getContentStreams() throws IOException {
return java.util.List.of(stream);
}
@Override
public RequestWriter.ContentWriter getContentWriter(String expectedType) {
- if (null == stream) {
+ if (null == contentWriter && null != stream) {
+ return super.getContentWriter(expectedType);
+ }
+ if (null == contentWriter) {
throw new NullPointerException(NO_STREAM_ERROR);
}
- return new RequestWriter.ContentWriter() {
- @Override
- public void write(OutputStream os) throws IOException {
- try (var inStream = stream.getStream()) {
- inStream.transferTo(os);
- }
- }
-
- @Override
- public String getContentType() {
- return stream.getContentType();
- }
- };
+ return contentWriter;
}
@Override
public SolrParams getParams() {
ModifiableSolrParams params = new
ModifiableSolrParams(super.getParams());
- if (null == stream) {
+ if (null == contentWriter && null == stream) {
throw new NullPointerException(NO_STREAM_ERROR);
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
index 9c19dfa64af..abb3749a97b 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
@@ -17,7 +17,6 @@
package org.apache.solr.client.solrj.request;
import java.io.IOException;
-import java.io.OutputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,6 +31,10 @@ import org.apache.solr.common.util.ContentStreamBase;
* <p>See
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-tika.html<br>
* See
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-update-handlers.html
*/
+/**
+ * @deprecated Use {@link ContentWriterUpdateRequest}.
+ */
+@Deprecated(since = "10.1")
public class ContentStreamUpdateRequest extends AbstractUpdateRequest {
List<ContentStream> contentStreams;
@@ -48,26 +51,6 @@ public class ContentStreamUpdateRequest extends
AbstractUpdateRequest {
return contentStreams;
}
- @Override
- public RequestWriter.ContentWriter getContentWriter(String expectedType) {
- if (contentStreams == null || contentStreams.isEmpty() ||
contentStreams.size() > 1)
- return null;
- ContentStream stream = contentStreams.get(0);
- return new RequestWriter.ContentWriter() {
- @Override
- public void write(OutputStream os) throws IOException {
- try (var inStream = stream.getStream()) {
- inStream.transferTo(os);
- }
- }
-
- @Override
- public String getContentType() {
- return stream.getContentType();
- }
- };
- }
-
/**
* Add a File to the {@link org.apache.solr.common.util.ContentStream}s.
*
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
new file mode 100644
index 00000000000..38e87d351e1
--- /dev/null
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.client.solrj.request;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.solr.client.solrj.request.RequestWriter.ContentWriter;
+import org.apache.solr.client.solrj.request.RequestWriter.NamedPart;
+
+/**
+ * Uploads one or more content parts to Solr Cell or another handler that
consumes request content
+ * (CSV, XML, JSON). A single part is sent as the request body; several parts
are sent as {@code
+ * multipart/form-data}.
+ *
+ * <p>See
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-tika.html<br>
+ * See
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-update-handlers.html
+ */
+public class ContentWriterUpdateRequest extends AbstractUpdateRequest {
+ private final List<NamedPart> parts = new ArrayList<>();
+
+ public ContentWriterUpdateRequest(String path) {
+ super(METHOD.POST, path);
+ }
+
+ @Override
+ public ContentWriter getContentWriter(String expectedType) {
+ if (parts.isEmpty()) return null;
+ if (parts.size() == 1) {
+ return parts.get(0).writer;
+ }
+ return new RequestWriter.MultipartContentWriter() {
+ @Override
+ public List<NamedPart> getParts() {
+ return List.copyOf(parts);
+ }
+ };
+ }
+
+ /** Adds a part written from {@code writer}, named {@code name} when sent as
multipart. */
+ public void addPart(String name, ContentWriter writer) {
+ parts.add(new NamedPart(name, writer));
+ }
+
+ /** Adds the file's bytes as a part, named after the file. */
+ public void addFile(Path file, String contentType) {
+ addPart(
+ file.getFileName().toString(),
+ new ContentWriter() {
+ @Override
+ public void write(OutputStream os) throws IOException {
+ Files.copy(file, os);
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+ });
+ }
+
+ /** Adds {@code content}, encoded as UTF-8, as the request's only part. */
+ public void addContentWithType(String content, String contentType) {
+ addPart(null, new RequestWriter.StringPayloadContentWriter(content,
contentType));
+ }
+}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
index 0d3ada79b3d..803ea981951 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
@@ -53,13 +53,13 @@ public class JavaBinRequestWriter extends RequestWriter {
}
@Override
+ @Deprecated(since = "7.6")
public Collection<ContentStream> getContentStreams(SolrRequest<?> req)
throws IOException {
if (req instanceof UpdateRequest updateRequest) {
if (isEmpty(updateRequest)) return null;
throw new RuntimeException("This Should not happen");
- } else {
- return req.getContentStreams();
}
+ return req.getContentStreams();
}
@Override
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
index f57cf2bafd7..967672ea748 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
@@ -43,20 +43,91 @@ public abstract class RequestWriter {
}
/**
- * To be implemented by subclasses to serialize update requests into the
appropriate format.
- *
- * <p>If this method returns null, {@link
- *
org.apache.solr.client.solrj.request.RequestWriter#getContentStreams(SolrRequest)}
is then
- * invoked to get content.
+ * A {@code multipart/form-data} body made of several named parts; build it
from {@link
+ * #getParts}.
*/
+ public interface MultipartContentWriter extends ContentWriter {
+
+ List<NamedPart> getParts();
+
+ @Override
+ default void write(OutputStream os) throws IOException {
+ throw new UnsupportedOperationException(
+ "Multipart content is written per-part via getParts(), not
write(OutputStream)");
+ }
+
+ @Override
+ default String getContentType() {
+ return "multipart/form-data";
+ }
+ }
+
+ /** One named part of a {@link MultipartContentWriter}'s body. */
+ public static final class NamedPart {
+ public final String name;
+ public final ContentWriter writer;
+
+ public NamedPart(String name, ContentWriter writer) {
+ this.name = name;
+ this.writer = writer;
+ }
+ }
+
+ /** To be implemented by subclasses to serialize update requests into the
appropriate format. */
public abstract ContentWriter getContentWriter(SolrRequest<?> req);
/**
- * @deprecated Use {@link #getContentWriter(SolrRequest)}.
+ * @deprecated Use {@link #getContentWriter(SolrRequest)}; this is consulted
only when that
+ * returns null.
*/
@Deprecated(since = "7.6")
- public abstract Collection<ContentStream> getContentStreams(SolrRequest<?>
req)
- throws IOException;
+ public Collection<ContentStream> getContentStreams(SolrRequest<?> req)
throws IOException {
+ return null;
+ }
+
+ /**
+ * The content to send for a request: {@link
#getContentWriter(SolrRequest)}, else any content
+ * from the deprecated {@link #getContentStreams(SolrRequest)}, else null.
+ */
+ @SuppressWarnings("deprecation")
+ public final ContentWriter getContentWriterOrStreams(SolrRequest<?> req)
throws IOException {
+ ContentWriter writer = getContentWriter(req);
+ return writer != null ? writer : contentWriterOf(getContentStreams(req));
+ }
+
+ /**
+ * Adapts streams to a writer: multipart when there are several, all named;
else the first.
+ *
+ * @return null if there are none
+ */
+ @Deprecated(since = "10.1")
+ public static ContentWriter contentWriterOf(Collection<ContentStream>
streams) {
+ if (streams == null || streams.isEmpty()) {
+ return null;
+ }
+ if (streams.size() > 1 && streams.stream().noneMatch(cs -> cs.getName() ==
null)) {
+ List<NamedPart> parts =
+ streams.stream().map(cs -> new NamedPart(cs.getName(),
contentWriterOf(cs))).toList();
+ return (MultipartContentWriter) () -> parts;
+ }
+ return contentWriterOf(streams.iterator().next());
+ }
+
+ private static ContentWriter contentWriterOf(ContentStream stream) {
+ return new ContentWriter() {
+ @Override
+ public void write(OutputStream os) throws IOException {
+ try (var in = stream.getStream()) {
+ in.transferTo(os);
+ }
+ }
+
+ @Override
+ public String getContentType() {
+ return stream.getContentType();
+ }
+ };
+ }
protected boolean isEmpty(UpdateRequest updateRequest) {
return isNull(updateRequest.getDocuments())
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
index 3620fb988ee..f801c9b30c1 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
@@ -61,6 +61,7 @@ public class XMLRequestWriter extends RequestWriter {
}
@Override
+ @Deprecated(since = "7.6")
public Collection<ContentStream> getContentStreams(SolrRequest<?> req)
throws IOException {
if (req instanceof UpdateRequest) {
return null;
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
index 32d8006ccca..01fecd1e6f8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
@@ -47,7 +47,13 @@ public class ClientUtils {
public static final String DEFAULT_PATH = "/select";
- /** Take a string and make it an iterable ContentStream */
+ /**
+ * Take a string and make it an iterable ContentStream
+ *
+ * @deprecated ContentStream is deprecated; use a {@link
+ * org.apache.solr.client.solrj.request.RequestWriter.ContentWriter}.
+ */
+ @Deprecated(since = "10.1")
public static Collection<ContentStream> toContentStreams(
final String str, final String contentType) {
if (str == null) return null;
diff --git
a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
index eeea5684324..91e4da4a2bc 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
@@ -28,7 +28,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.apache.solr.common.SolrException;
import org.noggit.JSONParser;
import org.noggit.ObjectBuilder;
@@ -297,47 +296,6 @@ public class CommandOperation {
return new String(toJSON(Map.of(name, commandData)),
StandardCharsets.UTF_8);
}
- public static List<CommandOperation> readCommands(
- Iterable<ContentStream> streams, @SuppressWarnings({"rawtypes"})
NamedList resp)
- throws IOException {
- return readCommands(streams, resp, Set.of());
- }
-
- /**
- * Read commands from request streams
- *
- * @param streams the streams
- * @param resp solr query response
- * @param singletonCommands , commands that cannot be repeated
- * @return parsed list of commands
- * @throws IOException if there is an error while parsing the stream
- */
- @SuppressWarnings({"unchecked"})
- public static List<CommandOperation> readCommands(
- Iterable<ContentStream> streams,
- @SuppressWarnings({"rawtypes"}) NamedList resp,
- Set<String> singletonCommands)
- throws IOException {
- if (streams == null) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "missing
content stream");
- }
- ArrayList<CommandOperation> ops = new ArrayList<>();
- for (ContentStream stream : streams) {
-
- if ("application/javabin".equals(stream.getContentType())) {
- ops.addAll(parse(stream.getStream(), singletonCommands));
- } else {
- ops.addAll(parse(stream.getReader(), singletonCommands));
- }
- }
- List<Map<String, Object>> errList = CommandOperation.captureErrors(ops);
- if (!errList.isEmpty()) {
- resp.add(CommandOperation.ERR_MSGS, errList);
- return null;
- }
- return ops;
- }
-
public static List<CommandOperation> clone(List<CommandOperation> ops) {
List<CommandOperation> opsCopy = new ArrayList<>(ops.size());
for (CommandOperation op : ops) opsCopy.add(op.getCopy());
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
index 0b671fb81d4..f5b29e44ab2 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
@@ -27,7 +27,6 @@ import java.net.ConnectException;
import java.net.SocketException;
import java.time.Instant;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -53,7 +52,6 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SolrNamedThreadFactory;
@@ -457,11 +455,6 @@ public class CloudSolrClientCacheTest extends
SolrTestCaseJ4 {
return params;
}
- @Override
- public Collection<ContentStream> getContentStreams() {
- return null;
- }
-
@Override
protected NamedList<Object> createResponse(NamedList<Object> namedList) {
return namedList;
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
index 357e061b17b..51c5a7bc5ee 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
@@ -48,13 +48,11 @@ public class TestConfigSetAdminRequest extends
SolrTestCaseJ4 {
verifyException(upload, "ConfigSet");
upload.setConfigSetName("name");
- verifyException(upload, "There must be a ContentStream");
+ verifyException(upload, "There must be content");
upload.setUploadFile(tmpFile, "application/zip");
- assertEquals(1, upload.getContentStreams().size());
- assertEquals(
- "application/zip",
upload.getContentStreams().stream().findFirst().get().getContentType());
+ assertEquals("application/zip",
upload.getContentWriter(null).getContentType());
assertNull(upload.getParams().get(ConfigSetParams.FILE_PATH));
assertNull(upload.getParams().get(ConfigSetParams.OVERWRITE));
@@ -65,9 +63,7 @@ public class TestConfigSetAdminRequest extends SolrTestCaseJ4
{
.setFilePath("solrconfig.xml")
.setOverwrite(true);
- assertEquals(1, upload.getContentStreams().size());
- assertEquals(
- "application/xml",
upload.getContentStreams().stream().findFirst().get().getContentType());
+ assertEquals("application/xml",
upload.getContentWriter(null).getContentType());
assertEquals("solrconfig.xml",
upload.getParams().get(ConfigSetParams.FILE_PATH));
assertEquals("true", upload.getParams().get(ConfigSetParams.OVERWRITE));
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
index 0717ab90c7d..93d2e2df946 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
@@ -24,7 +24,7 @@ import org.apache.solr.SolrTestCase;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -60,7 +60,7 @@ public class DirectJsonQueryRequestFacetingEmbeddedTest
extends SolrTestCase {
SolrClient client = solrTestRule.getSolrClient();
- ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
up.setParam("collection", client.getDefaultCollection());
up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
index 6e0c8b8891b..685a8dfee4a 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
@@ -20,7 +20,7 @@ package org.apache.solr.client.solrj.request.json;
import java.util.List;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -51,7 +51,7 @@ public class DirectJsonQueryRequestFacetingIntegrationTest
extends SolrCloudTest
CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
.process(cluster.getSolrClient());
- ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
up.setParam("collection", COLLECTION_NAME);
up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
index b6ed7c24958..24c0c5777b8 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
@@ -25,7 +25,7 @@ import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -58,7 +58,7 @@ public class JsonQueryRequestFacetingIntegrationTest extends
SolrCloudTestCase {
CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
.process(cluster.getSolrClient());
- ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
up.setParam("collection", COLLECTION_NAME);
up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
index b43310e8fe1..02e46872560 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
@@ -24,7 +24,7 @@ import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
@@ -55,7 +55,7 @@ public class JsonQueryRequestIntegrationTest extends
SolrCloudTestCase {
CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
.process(cluster.getSolrClient());
- ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
up.setParam("collection", COLLECTION_NAME);
up.addFile(getFile("solrj/books.csv"), "application/csv");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
index f76dcc9c3b0..0f53c315cde 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
@@ -26,12 +26,11 @@ import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
import org.apache.solr.client.solrj.response.InputStreamResponseParser;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.CloudConfig;
import org.apache.solr.core.CoreContainer;
@@ -265,8 +264,8 @@ public class TestHarness extends BaseTestHarness {
assert null != mdcSnap; // prevent compiler warning of unused var
EmbeddedSolrServer server = new EmbeddedSolrServer(getCoreContainer(),
getCore().getName());
- ContentStreamUpdateRequest xmlRequest = new
ContentStreamUpdateRequest("/update");
- xmlRequest.addContentStream(new ContentStreamBase.StringStream(xml,
"text/xml"));
+ ContentWriterUpdateRequest xmlRequest = new
ContentWriterUpdateRequest("/update");
+ xmlRequest.addContentWithType(xml, "text/xml");
// Request XML response format and use InputStreamResponseParser
xmlRequest.getParams().add("wt", "xml");