This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new ebc845c390 TIKA-4931: fix overrides in PipesForkParser (#3257)
ebc845c390 is described below
commit ebc845c390ab375722e3dd7e9b50b92cf88b99aa
Author: Tim Allison <[email protected]>
AuthorDate: Fri Sep 25 12:38:21 2026 -0400
TIKA-4931: fix overrides in PipesForkParser (#3257)
---
CHANGES.txt | 4 ++
.../tika/pipes/core/config/ConfigMerger.java | 7 +++
.../tika/pipes/core/config/ConfigOverrides.java | 44 ++++++++++++++++-
.../tika/pipes/core/config/ConfigMergerTest.java | 35 +++++++++++++
.../apache/tika/pipes/fork/PipesForkParser.java | 7 ++-
.../tika/pipes/fork/PipesForkParserConfig.java | 6 +++
.../tika/pipes/fork/PipesForkParserTest.java | 57 ++++++++++++++++++++++
7 files changed, 157 insertions(+), 3 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 3348b3049e..8625c7f0ef 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -89,6 +89,10 @@ Release 4.1.0 - 9/25/2026
crashes; tika-eval Profile/Compare read that ledger and the run-info
json to classify NO_EXTRACT_FILE by cause (TIKA-4846, TIKA-4847).
+ * PipesForkParser no longer drops javaPath and socketTimeoutMillis set in
+ code; the fork ran java from the PATH with a 60s socket timeout
+ regardless (TIKA-4931).
+
EXTRACTION AND FORMATS
* Improve extraction of tagged PDFs. New tika-eval-structure tool compares
diff --git
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigMerger.java
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigMerger.java
index 57d92625b5..f10ddd7a28 100644
---
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigMerger.java
+++
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigMerger.java
@@ -168,6 +168,13 @@ public class ConfigMerger {
pipesNode.put("maxFilesProcessedPerProcess",
pc.getMaxFilesProcessedPerProcess());
}
+ if (pc.getSocketTimeoutMillis() > 0) {
+ pipesNode.put("socketTimeoutMillis",
pc.getSocketTimeoutMillis());
+ }
+ if (pc.getJavaPath() != null) {
+ pipesNode.put("javaPath", pc.getJavaPath());
+ }
+
// Apply forked JVM args
List<String> jvmArgs = pc.getForkedJvmArgs();
if (jvmArgs != null && !jvmArgs.isEmpty()) {
diff --git
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigOverrides.java
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigOverrides.java
index 2c9e100ad5..779217b29a 100644
---
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigOverrides.java
+++
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/ConfigOverrides.java
@@ -150,14 +150,30 @@ public class ConfigOverrides {
private final int numClients;
private final int maxFilesProcessedPerProcess;
private final List<String> forkedJvmArgs;
+ private final long socketTimeoutMillis;
+ private final String javaPath;
public PipesConfigOverride(int numClients,
int maxFilesProcessedPerProcess,
List<String> forkedJvmArgs) {
+ this(numClients, maxFilesProcessedPerProcess, forkedJvmArgs, -1,
null);
+ }
+
+ /**
+ * @param socketTimeoutMillis written when positive; -1 leaves the
existing config's value
+ * @param javaPath written when non-null; null leaves the existing
config's value
+ */
+ public PipesConfigOverride(int numClients,
+ int maxFilesProcessedPerProcess,
+ List<String> forkedJvmArgs,
+ long socketTimeoutMillis,
+ String javaPath) {
this.numClients = numClients;
this.maxFilesProcessedPerProcess = maxFilesProcessedPerProcess;
this.forkedJvmArgs = forkedJvmArgs != null ?
new ArrayList<>(forkedJvmArgs) : new ArrayList<>();
+ this.socketTimeoutMillis = socketTimeoutMillis;
+ this.javaPath = javaPath;
}
public int getNumClients() {
@@ -171,6 +187,14 @@ public class ConfigOverrides {
public List<String> getForkedJvmArgs() {
return forkedJvmArgs;
}
+
+ public long getSocketTimeoutMillis() {
+ return socketTimeoutMillis;
+ }
+
+ public String getJavaPath() {
+ return javaPath;
+ }
}
/**
@@ -238,8 +262,26 @@ public class ConfigOverrides {
public Builder setPipesConfig(int numClients,
int maxFilesProcessedPerProcess,
List<String> forkedJvmArgs) {
+ return setPipesConfig(numClients, maxFilesProcessedPerProcess,
forkedJvmArgs,
+ -1, null);
+ }
+
+ /**
+ * Set pipes configuration including the parent-side process settings.
+ *
+ * @param socketTimeoutMillis socket read timeout for the forked
process, -1 to leave
+ * the existing config's value
+ * @param javaPath java executable for the forked process, null to
leave the existing
+ * config's value
+ * @return this builder
+ */
+ public Builder setPipesConfig(int numClients,
+ int maxFilesProcessedPerProcess,
+ List<String> forkedJvmArgs,
+ long socketTimeoutMillis,
+ String javaPath) {
this.pipesConfig = new PipesConfigOverride(numClients,
- maxFilesProcessedPerProcess, forkedJvmArgs);
+ maxFilesProcessedPerProcess, forkedJvmArgs,
socketTimeoutMillis, javaPath);
return this;
}
diff --git
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigMergerTest.java
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigMergerTest.java
index 69101bfa19..c7f551ed17 100644
---
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigMergerTest.java
+++
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigMergerTest.java
@@ -387,4 +387,39 @@ public class ConfigMergerTest {
Files.deleteIfExists(result.configPath());
}
+
+ /** TIKA-4931: socketTimeoutMillis and javaPath set in code reach the
merged file. */
+ @Test
+ public void testSocketTimeoutAndJavaPathWritten() throws IOException {
+ ConfigOverrides overrides = ConfigOverrides.builder()
+ .setPipesConfig(1, 100, List.of("-Xmx512m"), 1234,
"/opt/jdk/bin/java")
+ .build();
+
+ ConfigMerger.MergeResult result = ConfigMerger.mergeOrCreate(null,
overrides);
+
+ JsonNode pipes = new
ObjectMapper().readTree(result.configPath().toFile()).get("pipes");
+ assertEquals(1234, pipes.get("socketTimeoutMillis").asLong());
+ assertEquals("/opt/jdk/bin/java", pipes.get("javaPath").asText());
+
+ Files.deleteIfExists(result.configPath());
+ }
+
+ /** Unset (-1 / null) leaves the user config file's values alone. */
+ @Test
+ public void testSocketTimeoutAndJavaPathUnsetKeepUserValues() throws
IOException {
+ Path userConfig = tempDir.resolve("user-config.json");
+ Files.writeString(userConfig,
+
"{\"pipes\":{\"socketTimeoutMillis\":4321,\"javaPath\":\"/file/java\"}}");
+ ConfigOverrides overrides = ConfigOverrides.builder()
+ .setPipesConfig(1, 100, null, -1, null)
+ .build();
+
+ ConfigMerger.MergeResult result =
ConfigMerger.mergeOrCreate(userConfig, overrides);
+
+ JsonNode pipes = new
ObjectMapper().readTree(result.configPath().toFile()).get("pipes");
+ assertEquals(4321, pipes.get("socketTimeoutMillis").asLong());
+ assertEquals("/file/java", pipes.get("javaPath").asText());
+
+ Files.deleteIfExists(result.configPath());
+ }
}
diff --git
a/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParser.java
b/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParser.java
index cabf5bd43f..f9a6c35da4 100644
---
a/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParser.java
+++
b/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParser.java
@@ -409,11 +409,14 @@ public class PipesForkParser implements Closeable {
// Use null ID to trigger UUID generation
.addFetcher(null, "file-system-fetcher",
Map.of("allowAbsolutePaths", true))
- // Set pipes configuration
+ // socketTimeoutMillis/javaPath start unset in
PipesForkParserConfig, so a
+ // value here was set in code and overrides the user config
(TIKA-4931)
.setPipesConfig(
pc.getNumClients(),
pc.getMaxFilesProcessedPerProcess(),
- pc.getForkedJvmArgs())
+ pc.getForkedJvmArgs(),
+ pc.getSocketTimeoutMillis(),
+ pc.getJavaPath())
// Use PASSBACK_ALL strategy - results returned through socket
.setEmitStrategy(EmitStrategy.PASSBACK_ALL);
diff --git
a/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParserConfig.java
b/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParserConfig.java
index 8c498d2ad1..4e0ae1f4ce 100644
---
a/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParserConfig.java
+++
b/tika-pipes/tika-pipes-fork-parser/src/main/java/org/apache/tika/pipes/fork/PipesForkParserConfig.java
@@ -50,6 +50,10 @@ public class PipesForkParserConfig {
BasicContentHandlerFactory.HANDLER_TYPE.TEXT, -1);
// Default to single client for simple fork parser use case
this.pipesConfig.setNumClients(1);
+ // Unset, not defaulted: anything still null/-1 at merge time is left
to the user
+ // config file (else PipesConfig's default); anything else was set in
code and wins.
+ this.pipesConfig.setJavaPath(null);
+ this.pipesConfig.setSocketTimeoutMillis(-1);
}
/**
@@ -228,6 +232,8 @@ public class PipesForkParserConfig {
/**
* Set the Java executable path.
*
+ * Unset by default: the user config file's {@code javaPath}, else {@code
java}, applies.
+ *
* @param javaPath path to the java executable
* @return this config for chaining
*/
diff --git
a/tika-pipes/tika-pipes-fork-parser/src/test/java/org/apache/tika/pipes/fork/PipesForkParserTest.java
b/tika-pipes/tika-pipes-fork-parser/src/test/java/org/apache/tika/pipes/fork/PipesForkParserTest.java
index 832255c917..731cf9a1b8 100644
---
a/tika-pipes/tika-pipes-fork-parser/src/test/java/org/apache/tika/pipes/fork/PipesForkParserTest.java
+++
b/tika-pipes/tika-pipes-fork-parser/src/test/java/org/apache/tika/pipes/fork/PipesForkParserTest.java
@@ -107,6 +107,63 @@ public class PipesForkParserTest {
}
}
+ /** TIKA-4931: javaPath set in code must reach the process that starts the
fork. */
+ @Test
+ public void testJavaPathReachesFork() throws Exception {
+ Path testFile = tempDir.resolve("test.txt");
+ Files.writeString(testFile, "hello");
+ PipesForkParserConfig config = new PipesForkParserConfig()
+ .setPluginsDir(PLUGINS_DIR)
+ .setJavaPath(tempDir.resolve("no-such-java").toString());
+
+ try (PipesForkParser parser = new PipesForkParser(config);
+ TikaInputStream tis = TikaInputStream.get(testFile)) {
+ PipesForkParserException e =
+ assertThrows(PipesForkParserException.class, () ->
parser.parse(tis));
+ assertTrue(e.getMessage().contains("no-such-java"),
e.getMessage());
+ }
+ }
+
+ /** TIKA-4931: a code setting equal to the default still overrides the
user config file. */
+ @Test
+ public void testExplicitDefaultJavaPathBeatsUserConfig() throws Exception {
+ Path userConfig = tempDir.resolve("user-config.json");
+ Files.writeString(userConfig, "{\"pipes\":{\"javaPath\":\""
+ + tempDir.resolve("no-such-java").toString().replace("\\",
"\\\\") + "\"}}");
+ Path testFile = tempDir.resolve("test.txt");
+ Files.writeString(testFile, "hello");
+ PipesForkParserConfig config = new PipesForkParserConfig()
+ .setPluginsDir(PLUGINS_DIR)
+ .setUserConfigPath(userConfig)
+ .setJavaPath("java");
+
+ try (PipesForkParser parser = new PipesForkParser(config);
+ TikaInputStream tis = TikaInputStream.get(testFile)) {
+ PipesForkResult result = parser.parse(tis);
+ assertTrue(result.isSuccess(), "status: " + result.getStatus());
+ }
+ }
+
+ /** TIKA-4931: javaPath never set in code leaves the user config file's
value in force. */
+ @Test
+ public void testUnsetJavaPathLeavesUserConfig() throws Exception {
+ Path userConfig = tempDir.resolve("user-config.json");
+ Files.writeString(userConfig, "{\"pipes\":{\"javaPath\":\""
+ + tempDir.resolve("no-such-java").toString().replace("\\",
"\\\\") + "\"}}");
+ Path testFile = tempDir.resolve("test.txt");
+ Files.writeString(testFile, "hello");
+ PipesForkParserConfig config = new PipesForkParserConfig()
+ .setPluginsDir(PLUGINS_DIR)
+ .setUserConfigPath(userConfig);
+
+ try (PipesForkParser parser = new PipesForkParser(config);
+ TikaInputStream tis = TikaInputStream.get(testFile)) {
+ PipesForkParserException e =
+ assertThrows(PipesForkParserException.class, () ->
parser.parse(tis));
+ assertTrue(e.getMessage().contains("no-such-java"),
e.getMessage());
+ }
+ }
+
/**
* The inline payload is request-owned: a caller reusing the ParseContext
must not have this
* document's bytes retained and re-serialized into later requests.