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 8c8deae232 TIKA-4988: Activate catalog presets from the command line
and environment (TIKA_PRESETS) (#3383)
8c8deae232 is described below
commit 8c8deae2322e6aab5579824b1634ba6600e8c82b
Author: Dominik Schmidt <[email protected]>
AuthorDate: Wed Oct 21 14:56:06 2026 +0200
TIKA-4988: Activate catalog presets from the command line and environment
(TIKA_PRESETS) (#3383)
* TIKA-4988: Activate catalog presets from the command line and environment
tika-server takes --preset <name> (repeatable) and TIKA_PRESETS (comma
separated); both add "<name>": true to the config's presets before the
front end and the forks load it. The config file's own entry wins.
* TIKA-4988: --preset and TIKA_PRESETS override the config file's entry
* TIKA-4988: review follow-ups
Log what --preset and TIKA_PRESETS replace in the config file, hand
/async the config with the presets folded in, and refuse a presets
value that is not an object instead of overwriting it.
---------
Co-authored-by: Tim Allison <[email protected]>
---
CHANGES.txt | 5 ++
docs/modules/ROOT/pages/using-tika/docker.adoc | 11 +++
.../ROOT/pages/using-tika/server/index.adoc | 25 +++++--
.../tika/pipes/core/config/ConfigMerger.java | 11 +++
.../tika/pipes/core/config/ConfigOverrides.java | 17 +++++
.../tika/pipes/core/config/ConfigMergerTest.java | 37 ++++++++++
.../org/apache/tika/server/core/TikaServerCli.java | 3 +
.../apache/tika/server/core/TikaServerConfig.java | 75 ++++++++++++++++++-
.../apache/tika/server/core/TikaServerProcess.java | 22 +++++-
.../tika/server/core/IntegrationTestBase.java | 12 ++++
.../core/PresetActivationIntegrationTest.java | 83 ++++++++++++++++++++++
.../tika/server/core/TikaServerConfigTest.java | 39 ++++++++++
.../META-INF/tika/preset-catalog.properties | 19 +++++
.../test-presets/catalog-xml-content.json | 3 +
14 files changed, 354 insertions(+), 8 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index ef14a82210..819f1a9bcd 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,10 @@
Release 4.2.0 - unreleased
+ * tika-server activates catalog presets from the command line (--preset
<name>, repeatable)
+ and the environment (TIKA_PRESETS, comma-separated), the equivalent of
"<name>": true in
+ the config's presets, overriding the file's own entry for the name; a
config file needs
+ no longer be mounted for that (TIKA-4988).
+
BREAKING CHANGES AND DEPRECATIONS
* Extract stored line and paragraph counts from legacy Microsoft Word
(.doc) documents. The existing metadata properties are now populated
diff --git a/docs/modules/ROOT/pages/using-tika/docker.adoc
b/docs/modules/ROOT/pages/using-tika/docker.adoc
index efb07e6162..02e255da57 100644
--- a/docs/modules/ROOT/pages/using-tika/docker.adoc
+++ b/docs/modules/ROOT/pages/using-tika/docker.adoc
@@ -105,6 +105,17 @@ For example, to keep the full image's parsers but turn OCR
off:
See xref:configuration/index.adoc[Configuration] for the file format.
+A catalog preset needs no file: name it in `TIKA_PRESETS` (comma-separated) or
with `--preset`,
+and its route is live:
+
+[source,bash]
+----
+docker run -d -p 127.0.0.1:9998:9998 -e TIKA_PRESETS=thumbnail
apache/tika:X.Y.Z-full
+curl -T document.pdf http://localhost:9998/unpack/preset/thumbnail
+----
+
+See
xref:using-tika/server/index.adoc#_presets_named_configuration_without_config[Presets].
+
== Extra jars
Mount a directory of jars at `/tika-extras` — it is already on the container's
classpath, and the
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index cd073a78c0..7930aa5a01 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -129,8 +129,8 @@ Nothing is active unless the top-level `presets` key names
it. `true` activates
the classpath *catalog* (definitions shipped with Tika, so their content
tracks the Tika
version); an object defines a preset in place, replacing any same-named
catalog definition
wholesale; `false`/`null` is an explicit no-op. Catalog jars can never
activate themselves —
-every active preset is a visible line in the operator's config, and `true`
naming nothing in
-the catalog fails startup:
+every active preset is a visible line in the operator's config, command line
or environment,
+and `true` naming nothing in the catalog fails startup:
[source,json]
----
@@ -143,6 +143,18 @@ the catalog fails startup:
}
----
+A catalog preset can also be activated without touching the config file:
`--preset <name>`
+(repeatable) or the environment variable `TIKA_PRESETS` with a comma-separated
list. Either is
+the same as `"<name>": true` in `presets`, so it needs no `-c` at all, and the
catalog must know
+the name. Both win over the config file: a name listed there is active with
the catalog's
+content even if the file sets it to `false` or defines it inline.
+
+[source,bash]
+----
+java -jar tika-server-standard-X.Y.Z.jar --preset thumbnail --preset
render-thumbnail
+TIKA_PRESETS=thumbnail,render-thumbnail java -jar
tika-server-standard-X.Y.Z.jar
+----
+
Presets are deliberately narrow: a request selects exactly one, the preset
routes take no
`config` part, and a preset never combines with request-supplied configuration
— a variant is
another preset. Because preset content is operator- or Tika-vetted, the preset
routes do *not*
@@ -361,12 +373,17 @@ The server starts on `localhost:9998` by default.
|Enable Prometheus metrics on this port. See
xref:using-tika/server/monitoring.adoc[Monitoring with Prometheus].
+|`--preset <name>`
+|Activate a catalog preset, as `"presets": {"<name>": true}` in the config
would. Repeatable.
+The environment variable `TIKA_PRESETS` takes a comma-separated list. See
<<_presets_named_configuration_without_config,Presets>>.
+
|`-?`, `--help`
|Print the usage message.
|===
-NOTE: `-h`, `-p`, `-i` and `--metricsPort` override the JSON config.
Everything else — `allowPipes`,
-`allowPerRequestConfig`, CORS, TLS, timeouts — is JSON-only.
+NOTE: `-h`, `-p`, `-i` and `--metricsPort` override the JSON config;
`--preset` and `TIKA_PRESETS`
+add to its `presets`. Everything else — `allowPipes`, `allowPerRequestConfig`,
CORS, TLS,
+timeouts — is JSON-only.
== Endpoints
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 7432943a42..7f6b00bf83 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
@@ -230,6 +230,17 @@ public class ConfigMerger {
getOrCreateObject(mapper, root, "pipes").put("trustedRequests",
true);
}
+ if (!overrides.getPresets().isEmpty()) {
+ if (root.has("presets") && !root.get("presets").isObject()) {
+ throw new IOException("'presets' must be an object of preset
definitions"
+ + (existingConfig == null ? "" : " in " +
existingConfig));
+ }
+ ObjectNode presetsNode = getOrCreateObject(mapper, root,
"presets");
+ for (String preset : overrides.getPresets()) {
+ presetsNode.put(preset, true);
+ }
+ }
+
// Apply emit strategy
if (overrides.getEmitStrategy() != null) {
ObjectNode pipesNode = getOrCreateObject(mapper, root, "pipes");
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 bb2c88f13c..3ecb005ad4 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
@@ -56,6 +56,7 @@ public class ConfigOverrides {
private final Integer writeLimit;
private final boolean classpathPlugins;
private final boolean trustedRequests;
+ private final List<String> presets;
private ConfigOverrides(Builder builder) {
this.fetchers = Collections.unmodifiableList(new
ArrayList<>(builder.fetchers));
@@ -68,6 +69,7 @@ public class ConfigOverrides {
this.writeLimit = builder.writeLimit;
this.classpathPlugins = builder.classpathPlugins;
this.trustedRequests = builder.trustedRequests;
+ this.presets = Collections.unmodifiableList(new
ArrayList<>(builder.presets));
}
public static Builder builder() {
@@ -116,6 +118,11 @@ public class ConfigOverrides {
return trustedRequests;
}
+ /** Catalog presets to activate, as {@code "name": true} would; see
ConfigMerger. */
+ public List<String> getPresets() {
+ return presets;
+ }
+
/**
* Represents a fetcher configuration override.
*/
@@ -238,10 +245,20 @@ public class ConfigOverrides {
private Integer writeLimit;
private boolean classpathPlugins;
private boolean trustedRequests;
+ private final List<String> presets = new ArrayList<>();
private Builder() {
}
+ /**
+ * Activates a catalog preset, as {@code "presets": {"name": true}} in
the config would,
+ * replacing whatever value the existing config has for the name.
+ */
+ public Builder addPreset(String name) {
+ this.presets.add(name);
+ return this;
+ }
+
/**
* Sets {@code pipes.trustedRequests}: the requests come from the
operator, so their
* parseContext is trusted and their limits and timeouts are used as
given.
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 98ed690781..b0a4f9f1d8 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
@@ -107,6 +107,43 @@ public class ConfigMergerTest {
assertEquals(50,
parseContext.get("output-limits").get("maxXmlDepth").asInt());
}
+ @Test
+ public void testPresetsOverrideTheConfigsOwnEntries() throws IOException {
+ Path existing = tempDir.resolve("existing.json");
+ Files.writeString(existing, "{\"presets\": {\"off\": false,
\"inline\": {\"pages\": {}}}}");
+ ConfigOverrides overrides = ConfigOverrides.builder()
+ .addPreset("thumbnail")
+ .addPreset("off")
+ .addPreset("inline")
+ .build();
+
+ JsonNode presets = new ObjectMapper()
+ .readTree(ConfigMerger.merge(existing, overrides).json())
+ .get("presets");
+
+ assertTrue(presets.get("thumbnail").asBoolean());
+ assertTrue(presets.get("off").asBoolean());
+ assertTrue(presets.get("inline").asBoolean());
+ assertEquals(3, presets.size());
+ }
+
+ @Test
+ public void testPresetsRefuseAMalformedPresetsValue() throws IOException {
+ Path existing = tempDir.resolve("existing.json");
+ Files.writeString(existing, "{\"presets\": \"thumbnail\"}");
+ ConfigOverrides overrides =
ConfigOverrides.builder().addPreset("thumbnail").build();
+ assertThrows(IOException.class, () -> ConfigMerger.merge(existing,
overrides));
+ // without presets to fold in, the value is left for the loader to
refuse
+ assertNotNull(ConfigMerger.merge(existing,
ConfigOverrides.builder().build()));
+ }
+
+ @Test
+ public void testPresetsWithoutExistingConfig() throws IOException {
+ ConfigOverrides overrides =
ConfigOverrides.builder().addPreset("thumbnail").build();
+ JsonNode root = new ObjectMapper().readTree(ConfigMerger.merge(null,
overrides).json());
+ assertTrue(root.get("presets").get("thumbnail").asBoolean());
+ }
+
/** TIKA-4834: the config docs permit comments; the merge step must not
reject them. */
@Test
public void testMergeWithCommentedConfig() throws IOException {
diff --git
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerCli.java
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerCli.java
index 49dab1b970..1adb5ed060 100644
---
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerCli.java
+++
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerCli.java
@@ -42,6 +42,9 @@ public class TikaServerCli {
options.addOption(null, "metricsPort", true,
"serve Prometheus metrics on this port (9404 by convention); "
+ "unset means metrics are off\n");
+ options.addOption(null, "preset", true,
+ "activate a catalog preset, as \"presets\": {\"<name>\": true}
in the config "
+ + "would; repeatable, and TIKA_PRESETS takes a
comma-separated list");
return options;
}
diff --git
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
index 13774917ef..6747f953ac 100644
---
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
+++
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
@@ -22,10 +22,13 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.cli.CommandLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,12 +38,17 @@ import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.pipes.core.PipesConfig;
+import org.apache.tika.pipes.core.config.ConfigMerger;
+import org.apache.tika.pipes.core.config.ConfigOverrides;
public class TikaServerConfig {
public static final int DEFAULT_PORT = 9998;
public static final String DEFAULT_HOST = "localhost";
private static final Logger LOG =
LoggerFactory.getLogger(TikaServerConfig.class);
+
+ /** Comma-separated catalog presets to activate, read at startup like
{@code --preset}. */
+ public static final String PRESETS_ENV = "TIKA_PRESETS";
/**
* Endpoints that expose the pipes/fetch machinery (process-isolated pipes
* parsing and async batch processing). Selecting any of these requires
@@ -73,6 +81,7 @@ public class TikaServerConfig {
private String host = DEFAULT_HOST;
private String requestLogLevel = "";
private Path configPath;
+ private List<String> presets = new ArrayList<>();
private ArrayList<String> endpoints = new ArrayList<>();
private TlsConfig tlsConfig = new TlsConfig();
@@ -86,15 +95,37 @@ public class TikaServerConfig {
}
public static TikaServerConfig load(CommandLine commandLine) throws
IOException, TikaException {
+ return load(commandLine, System.getenv(PRESETS_ENV));
+ }
+
+ static TikaServerConfig load(CommandLine commandLine, String presetsEnv)
+ throws IOException, TikaException {
+
+ Map<String, String> presets = new LinkedHashMap<>();
+ if (commandLine.hasOption("preset")) {
+ for (String name : commandLine.getOptionValues("preset")) {
+ presets.putIfAbsent(name.trim(), "--preset");
+ }
+ }
+ if (presetsEnv != null) {
+ for (String name : presetsEnv.split(",")) {
+ if (!name.isBlank()) {
+ presets.putIfAbsent(name.trim(), PRESETS_ENV);
+ }
+ }
+ }
TikaServerConfig config = null;
Set<String> settings = new HashSet<>();
if (commandLine.hasOption("c")) {
- config = load(Paths.get(commandLine.getOptionValue("c")),
commandLine, settings);
+ config = load(Paths.get(commandLine.getOptionValue("c")),
commandLine, settings,
+ presets);
} else {
config = new TikaServerConfig();
+ presets.forEach((name, source) -> LOG.info("preset '{}' activated
by {}", name, source));
}
+ config.presets = new ArrayList<>(presets.keySet());
//port, host and id can be overwritten on the commandline at runtime
if (commandLine.hasOption("p")) {
@@ -127,7 +158,26 @@ public class TikaServerConfig {
}
static TikaServerConfig load(Path tikaConfigPath, CommandLine commandLine,
Set<String> settings) throws IOException, TikaException {
+ return load(tikaConfigPath, commandLine, settings, Map.of());
+ }
+
+ private static TikaServerConfig load(Path tikaConfigPath, CommandLine
commandLine,
+ Set<String> settings, Map<String,
String> presets)
+ throws IOException, TikaException {
TikaJsonConfig tikaJsonConfig =
TikaLoader.load(tikaConfigPath).getConfig();
+ JsonNode filePresets = tikaJsonConfig.getRootNode().get("presets");
+ presets.forEach((name, source) -> {
+ JsonNode entry = filePresets == null ? null :
filePresets.get(name);
+ if (entry == null) {
+ LOG.info("preset '{}' activated by {}", name, source);
+ } else if (entry.isObject()) {
+ LOG.warn("preset '{}' activated by {}: the catalog preset
replaces the inline "
+ + "definition in {}", name, source, tikaConfigPath);
+ } else {
+ LOG.warn("preset '{}' activated by {}: overrides \"{}\": {} in
{}", name, source,
+ name, entry, tikaConfigPath);
+ }
+ });
TikaServerConfig tikaServerConfig =
tikaJsonConfig.deserialize("server", TikaServerConfig.class);
if (tikaServerConfig == null) {
throw new TikaConfigException("Couldn't find 'server' element");
@@ -251,6 +301,29 @@ public class TikaServerConfig {
return configPath != null;
}
+ /**
+ * Catalog presets named with {@code --preset} or in {@value
#PRESETS_ENV}, activated like
+ * {@code "presets": {"name": true}} in the config, over the config's own
entry for the name.
+ */
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ public List<String> getPresets() {
+ return presets;
+ }
+
+ /**
+ * The config file with {@link #getPresets()} folded in: the file itself
when there are
+ * none, else a temp copy, for components that load their configuration
from a path.
+ */
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ public Path getConfigPathWithPresets() throws IOException {
+ if (presets.isEmpty()) {
+ return configPath;
+ }
+ ConfigOverrides.Builder overrides = ConfigOverrides.builder();
+ presets.forEach(overrides::addPreset);
+ return ConfigMerger.mergeOrCreate(configPath,
overrides.build()).configPath();
+ }
+
@com.fasterxml.jackson.annotation.JsonIgnore
public Path getConfigPath() {
return configPath;
diff --git
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
index c8713ca74f..a986773f0b 100644
---
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
+++
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
@@ -125,6 +125,9 @@ public class TikaServerProcess {
options.addOption(null, "metricsPort", true,
"serve Prometheus metrics on this port (9404 by convention); "
+ "unset means metrics are off\n");
+ options.addOption(null, "preset", true,
+ "activate a catalog preset, as \"presets\": {\"<name>\": true}
in the config "
+ + "would; repeatable, and TIKA_PRESETS takes a
comma-separated list");
options.addOption("?", "help", false, "this help message");
return options;
}
@@ -262,6 +265,15 @@ public class TikaServerProcess {
if (tikaServerConfig.hasConfigFile()) {
LOG.info("Using custom config: {}",
tikaServerConfig.getConfigPath());
+ }
+ if (!tikaServerConfig.getPresets().isEmpty()) {
+ // the front end's copy; createServerConfig gives the forks theirs
+ ConfigOverrides.Builder presets = ConfigOverrides.builder();
+ tikaServerConfig.getPresets().forEach(presets::addPreset);
+ Path existing = tikaServerConfig.hasConfigFile() ?
tikaServerConfig.getConfigPath() : null;
+ tikaLoader = TikaLoader.load(ConfigMerger.merge(existing,
presets.build()).load(),
+ Thread.currentThread().getContextClassLoader());
+ } else if (tikaServerConfig.hasConfigFile()) {
tikaLoader = TikaLoader.load(tikaServerConfig.getConfigPath());
} else {
tikaLoader = TikaLoader.loadDefault();
@@ -537,7 +549,8 @@ public class TikaServerProcess {
}
if (addAsyncResource) {
- final AsyncResource localAsyncResource = new
AsyncResource(tikaServerConfig.getConfigPath());
+ final AsyncResource localAsyncResource =
+ new
AsyncResource(tikaServerConfig.getConfigPathWithPresets());
localAsyncResource.setMaxQueuePauseMillis(tikaServerConfig.getMaxQueuePauseMillis());
Runtime
.getRuntime()
@@ -701,7 +714,7 @@ public class TikaServerProcess {
Path existingConfigPath = tikaServerConfig.hasConfigFile() ?
tikaServerConfig.getConfigPath() : null;
TikaJsonConfig tikaJsonConfig = createServerConfig(existingConfigPath,
- inputTempDirectory, unpackTempDirectory);
+ inputTempDirectory, unpackTempDirectory,
tikaServerConfig.getPresets());
// Load or create PipesConfig with defaults
PipesConfig pipesConfig = tikaJsonConfig.deserialize("pipes",
PipesConfig.class);
@@ -746,11 +759,13 @@ public class TikaServerProcess {
* @param existingConfigPath the existing config file path (may be null)
* @param inputTempDirectory the temp directory for input files
* @param unpackTempDirectory the temp directory for unpack output files
(may be null)
+ * @param presets catalog presets activated on the command line or in
TIKA_PRESETS
* @return the merged config
*/
private static TikaJsonConfig createServerConfig(Path existingConfigPath,
Path inputTempDirectory,
- Path unpackTempDirectory)
+ Path unpackTempDirectory,
+ List<String> presets)
throws IOException, TikaConfigException {
LOG.info("Configuring {} with basePath={}",
PipesParsingHelper.DEFAULT_FETCHER_ID, inputTempDirectory);
@@ -765,6 +780,7 @@ public class TikaServerProcess {
.setEmitStrategy(EmitStrategy.PASSBACK_ALL)
// Set plugin roots
.setPluginRoots(DefaultPluginsDir.resolve(TikaServerProcess.class));
+ presets.forEach(builder::addPreset);
// Only set default pipes config if there's no existing config
// This allows user-provided config to specify their own numClients,
etc.
diff --git
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java
index 24aaf28cfd..bebc5d8267 100644
---
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java
+++
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java
@@ -32,6 +32,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -171,7 +172,17 @@ public class IntegrationTestBase extends TikaTest {
process = start(extraArgs, TEMP_WORKING_DIR);
}
+ public void startProcess(String[] extraArgs, Map<String, String>
environment)
+ throws IOException {
+ process = start(extraArgs, TEMP_WORKING_DIR, environment);
+ }
+
private Process start(String[] extraArgs, Path workingDir) throws
IOException {
+ return start(extraArgs, workingDir, Map.of());
+ }
+
+ private Process start(String[] extraArgs, Path workingDir, Map<String,
String> environment)
+ throws IOException {
String[] base = new String[]{"java",
"-Djava.io.tmpdir=" + workingDir.toAbsolutePath(), // make
sure we're using subdir cleaned up by JUnit
"-cp", System.getProperty("java.class.path"),
"org.apache.tika.server.core.TikaServerCli",
@@ -179,6 +190,7 @@ public class IntegrationTestBase extends TikaTest {
List<String> args = new ArrayList<>(Arrays.asList(base));
args.addAll(Arrays.asList(extraArgs));
ProcessBuilder pb = new ProcessBuilder(args);
+ pb.environment().putAll(environment);
pb.inheritIO();
// pb.redirectInput(Files.createTempFile(STREAMS_DIR,
"tika-stream-out", ".log").toFile());
// pb.redirectError(Files.createTempFile(STREAMS_DIR,
diff --git
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/PresetActivationIntegrationTest.java
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/PresetActivationIntegrationTest.java
new file mode 100644
index 0000000000..8f20c3ca3b
--- /dev/null
+++
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/PresetActivationIntegrationTest.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.tika.server.core;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.serialization.JsonMetadataList;
+
+/**
+ * A catalog preset activated with {@code --preset} or {@code TIKA_PRESETS}
instead of the
+ * config's {@code presets} block, resolved by the front end and the forked
worker alike. The
+ * catalog entry is the test classpath's {@code catalog-xml-content}.
+ */
+public class PresetActivationIntegrationTest extends IntegrationTestBase {
+
+ private static final String PRESET = "catalog-xml-content";
+
+ @Test
+ public void testPresetOption() throws Exception {
+ startProcess(new String[]{"-config",
getConfig("tika-config-server-basic.json"),
+ "--preset", PRESET});
+ assertPresetServesXml();
+ }
+
+ @Test
+ public void testPresetsEnvironmentVariable() throws Exception {
+ startProcess(new String[]{"-config",
getConfig("tika-config-server-basic.json")},
+ Map.of(TikaServerConfig.PRESETS_ENV, " " + PRESET + ", "));
+ assertPresetServesXml();
+ }
+
+ @Test
+ public void testUnknownPresetFailsStartup() throws Exception {
+ startProcess(new String[]{"-config",
getConfig("tika-config-server-basic.json"),
+ "--preset", "no-such-preset"});
+ assertTrue(process.waitFor(MAX_WAIT_MS, TimeUnit.MILLISECONDS),
"server should exit");
+ assertNotEquals(0, process.exitValue());
+ }
+
+ private void assertPresetServesXml() throws Exception {
+ awaitServerStartup();
+ Response response = WebClient
+ .create(endPoint + RMETA_PATH + "/preset/" + PRESET)
+ .accept("application/json")
+ .put(ClassLoader.getSystemResourceAsStream(TEST_HELLO_WORLD));
+ assertEquals(200, response.getStatus());
+ Reader reader = new InputStreamReader((InputStream)
response.getEntity(), UTF_8);
+ List<Metadata> metadataList = JsonMetadataList.fromJson(reader);
+ assertEquals(1, metadataList.size());
+ assertContains("<body><p>hello world</p>",
+ metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
+ }
+}
diff --git
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
index 2b516a65a0..7ce1bbb390 100644
---
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
+++
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
@@ -18,14 +18,18 @@ package org.apache.tika.server.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Path;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
@@ -81,6 +85,41 @@ public class TikaServerConfigTest extends TikaTest {
assertEquals(60000L, TikaServerConfig.DEFAULT_MAX_QUEUE_PAUSE_MILLIS);
}
+ @Test
+ public void testPresetsFromCommandLineAndEnvironment() throws Exception {
+ Options options = new
Options().addOption(Option.builder().longOpt("preset").hasArg().get());
+ CommandLine commandLine = new DefaultParser().parse(options,
+ new String[]{"--preset", "thumbnail", "--preset",
"render-thumbnail"});
+
+ TikaServerConfig config = TikaServerConfig.load(commandLine, "
render-thumbnail, ,no-ocr ");
+ assertEquals(List.of("thumbnail", "render-thumbnail", "no-ocr"),
config.getPresets());
+
+ config = TikaServerConfig.load(commandLine, null);
+ assertEquals(List.of("thumbnail", "render-thumbnail"),
config.getPresets());
+
+ config = TikaServerConfig.load(new DefaultParser().parse(options, new
String[]{}), null);
+ assertTrue(config.getPresets().isEmpty());
+ }
+
+ @Test
+ public void testConfigPathWithPresets() throws Exception {
+ Path path = getConfigPath(getClass(), "tika-config-server.json");
+ Options options = new Options()
+ .addOption(Option.builder().longOpt("preset").hasArg().get())
+
.addOption(Option.builder("c").longOpt("config").hasArg().get());
+ String[] args = {"-c",
ProcessUtils.escapeCommandLine(path.toAbsolutePath().toString())};
+
+ TikaServerConfig config = TikaServerConfig.load(new
DefaultParser().parse(options, args), null);
+ assertEquals(path.toAbsolutePath(), config.getConfigPathWithPresets());
+
+ config = TikaServerConfig.load(new DefaultParser().parse(options,
args), "thumbnail");
+ Path merged = config.getConfigPathWithPresets();
+ assertNotEquals(path.toAbsolutePath(), merged);
+ JsonNode root = new ObjectMapper().readTree(merged.toFile());
+ assertTrue(root.get("presets").get("thumbnail").asBoolean());
+ assertTrue(root.has("server"));
+ }
+
@Test
public void testPorts() throws Exception {
CommandLineParser parser = new DefaultParser();
diff --git
a/tika-server/tika-server-core/src/test/resources/META-INF/tika/preset-catalog.properties
b/tika-server/tika-server-core/src/test/resources/META-INF/tika/preset-catalog.properties
new file mode 100644
index 0000000000..b687a83138
--- /dev/null
+++
b/tika-server/tika-server-core/src/test/resources/META-INF/tika/preset-catalog.properties
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+# inert until --preset, TIKA_PRESETS or a config activates it
+catalog-xml-content=/test-presets/catalog-xml-content.json
diff --git
a/tika-server/tika-server-core/src/test/resources/test-presets/catalog-xml-content.json
b/tika-server/tika-server-core/src/test/resources/test-presets/catalog-xml-content.json
new file mode 100644
index 0000000000..572b142162
--- /dev/null
+++
b/tika-server/tika-server-core/src/test/resources/test-presets/catalog-xml-content.json
@@ -0,0 +1,3 @@
+{
+ "basic-content-handler-factory": {"type": "XML"}
+}