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 5d180d2d89 TIKA-5059 (#3570)
5d180d2d89 is described below
commit 5d180d2d89c3f3b6ce1da72f1b7cf68ffc442caa
Author: Tim Allison <[email protected]>
AuthorDate: Mon Nov 9 17:13:14 2026 -0500
TIKA-5059 (#3570)
* TIKA-5059: warn on parse-context blocks for components that never read
them; @RequestConfigurable marks the ones that do
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
CHANGES.txt | 6 +
.../ROOT/pages/advanced/language-detection.adoc | 9 +-
docs/modules/ROOT/pages/configuration/index.adoc | 18 ++-
.../ROOT/pages/developers/serialization.adoc | 11 ++
.../apache/tika/config/RequestConfigurable.java | 50 +++++++
.../tika/parser/ocr/tess4j/Tess4JParser.java | 2 +
.../apache/tika/parser/vlm/ClaudeVLMParser.java | 2 +
.../apache/tika/parser/vlm/GeminiVLMParser.java | 2 +
.../apache/tika/parser/vlm/OpenAIVLMParser.java | 2 +
.../tika/parser/executable/ExecutableParser.java | 2 +
.../org/apache/tika/parser/image/BPGParser.java | 2 +
.../org/apache/tika/parser/image/Cr3Parser.java | 2 +
.../org/apache/tika/parser/image/HeifParser.java | 2 +
.../org/apache/tika/parser/image/JpegParser.java | 2 +
.../apache/tika/parser/image/RawTiffParser.java | 2 +
.../org/apache/tika/parser/image/TiffParser.java | 2 +
.../org/apache/tika/parser/image/WebPParser.java | 2 +
.../apache/tika/parser/microsoft/EMFParser.java | 2 +
.../apache/tika/parser/microsoft/WMFParser.java | 2 +
.../apache/tika/parser/ocr/TesseractOCRParser.java | 2 +
.../java/org/apache/tika/parser/pdf/PDFParser.java | 2 +
.../tika/serialization/ComponentNameResolver.java | 17 +++
.../tika/serialization/ConfigDeserializer.java | 48 +++++++
.../serdes/ParseContextDeserializer.java | 23 ++++
.../example/tikatest/CustomReaderTestParser.java | 55 ++++++++
.../config/loader/RequestConfigTestParser.java | 70 ++++++++++
.../loader/RequestConfigTestPlainSubParser.java | 26 ++++
.../config/loader/RequestConfigTestSubParser.java | 33 +++++
.../config/loader/UnreadWarningTestParser.java | 59 +++++++++
.../serialization/RequestConfigurableTest.java | 147 +++++++++++++++++++++
.../TestParseContextSerialization.java | 12 +-
.../WireRestrictedParseContextTest.java | 4 +-
.../serdes/UnreadBlockWarningTest.java | 34 +++++
.../tika/server/standard/TikaResourceTest.java | 17 +++
34 files changed, 655 insertions(+), 16 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 3718dad971..630012c002 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -50,6 +50,12 @@ Release 4.3.0 - ???
BoilerpipeContentHandler (tika-handler-boilerpipe) is deprecated
(TIKA-5054).
SERVER, PIPES AND OPERATIONS
+ * A parse-context block (config file, preset, tika-server /config, gRPC
+ parse_context_json, pipes FetchEmitTuple) for a parser, detector,
renderer or
+ translator that never reads one is logged as a warning instead of being
silently
+ ignored; Tika 5.0 will refuse it. Parsers that read a block carry the new
+ @RequestConfigurable annotation, which a custom parser that reads its own
block
+ should add; until then it still reads it, and a warning names the class
(TIKA-5059).
* tika-server sets any field of the config's "server" block from the
command line
(--serverSetting <field>=<value>, repeatable) and the environment
(TIKA_SERVER_SETTING_<FIELD>,
diff --git a/docs/modules/ROOT/pages/advanced/language-detection.adoc
b/docs/modules/ROOT/pages/advanced/language-detection.adoc
index 29dd60ca25..5cfd77f93b 100644
--- a/docs/modules/ROOT/pages/advanced/language-detection.adoc
+++ b/docs/modules/ROOT/pages/advanced/language-detection.adoc
@@ -143,12 +143,9 @@ threshold is meaningful for a short snippet and a full
document. Per-class
=== Tuning
`CharSoupLanguageDetector` is registered as
-`@TikaComponent(name = "charsoup-language-detector")` and implements
-`SelfConfiguring`, so it resolves by friendly name in the `parse-context` JSON
-section — see
-xref:migration-to-4x/serialization-4x.adoc#discovering-friendly-names[Discovering
the friendly name for a component].
-It exposes no config record yet; the programmatic knobs are `setMaxLength(int)`
-for the rolling buffer and `setPriors(Map)` for language priors.
+`@TikaComponent(name = "charsoup-language-detector")`. It exposes no config
record
+yet, so a `parse-context` block for it has no effect; the programmatic knobs
are
+`setMaxLength(int)` for the rolling buffer and `setPriors(Map)` for language
priors.
== Training the model
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc
b/docs/modules/ROOT/pages/configuration/index.adoc
index 8ad2251236..acdce6b8de 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -47,7 +47,7 @@ optional; anything you omit uses its defaults.
"exception-reporting": { /* how much exception detail is reported */ },
"unpack-config": { /* embedded-byte extraction */ },
"pages": { /* rendering, OCR policy and page images for every parser that
renders; see below */ }
- /* other SelfConfiguring components by component name */
+ /* parsers that read a block of their own, by component name; see below */
},
"server": { /* tika-server options: allowPipes, allowPerRequestConfig, cors,
tlsConfig, ... */ },
"grpc": { /* tika-grpc options */ },
@@ -83,7 +83,8 @@ produces no error and no effect:
* the `metadata-list` body — only `maxStringLength`, `maxNestingDepth`,
`maxNumberLength` and
`typedValues` are read;
* the body of a self-configuring component under `parse-context` — it is
handed to the component
- untouched;
+ untouched (a block for a component that does not read one has no effect and
is logged as a
+ warning; see <<parse-context-components>>);
* `_mime-include` / `_mime-exclude` when the value is neither a JSON array nor
a string — a bare
string is read as a one-element list, but any other shape yields an empty
filter rather than a
failure;
@@ -94,6 +95,19 @@ produces no error and no effect:
If a setting appears to do nothing, check its spelling against one of those
four first.
+[#parse-context-components]
+=== Parsers that read a `parse-context` block
+
+Every parser is self-configuring, but only these read a block of their own
under `parse-context`,
+whether from the config file, a preset, a tika-server `/config` request, a gRPC
+`parse_context_json` or a pipes `FetchEmitTuple`: `pdf-parser`,
`tesseract-ocr-parser`,
+`tess4j-parser`, `claude-vlm-parser`, `gemini-vlm-parser`,
`openai-vlm-parser`, `emf-parser`,
+`wmf-parser`, `executable-parser`, `jpeg-parser`, `tiff-parser`,
`raw-tiff-parser`,
+`heif-parser`, `bpg-parser`, `cr3-parser` and `web-p-parser`. A block for any
other parser,
+detector, renderer or translator has no effect: Tika logs a warning naming the
component, once
+per component per process, and Tika 5.0 will refuse it. Configure those
components in their own
+section (`parsers`, `detectors`, ...) instead.
+
[#typed-values]
=== Typed values in metadata JSON (`typedValues`)
diff --git a/docs/modules/ROOT/pages/developers/serialization.adoc
b/docs/modules/ROOT/pages/developers/serialization.adoc
index 5572324295..9ecffd1c5f 100644
--- a/docs/modules/ROOT/pages/developers/serialization.adoc
+++ b/docs/modules/ROOT/pages/developers/serialization.adoc
@@ -189,6 +189,7 @@ it needs it. `Parser` extends `SelfConfiguring`, so every
parser is self-configu
[source,java]
----
+@RequestConfigurable
@TikaComponent
public class PDFParser implements Parser, RenderingParser {
@@ -205,6 +206,16 @@ public class PDFParser implements Parser, RenderingParser {
}
----
+A component that reads such a block must also carry `@RequestConfigurable`. It
is not
+inherited: a subclass registered under its own name carries it too if it reads
that name's block.
+A `parse-context` block for a self-configuring component without it is logged
as a warning when
+the context is read, because nothing reads it, and Tika 5.0 will refuse it. A
custom component
+that reads its block without the annotation still gets it, and Tika logs a
warning, once, naming
+the class; add the annotation before 5.0. (For Tika's own components a missing
annotation fails
+an assertion on every `ParseContextConfig.getConfig` call, block or not, so
any test that reaches
+that call catches it; a component that calls it only when a block is present
is caught only by a
+test that sends one.)
+
Benefits:
* Per-request configuration via `ParseContext`
diff --git
a/tika-core/src/main/java/org/apache/tika/config/RequestConfigurable.java
b/tika-core/src/main/java/org/apache/tika/config/RequestConfigurable.java
new file mode 100644
index 0000000000..5a80c3d473
--- /dev/null
+++ b/tika-core/src/main/java/org/apache/tika/config/RequestConfigurable.java
@@ -0,0 +1,50 @@
+/*
+ * 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.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a {@link SelfConfiguring} component that reads a {@code
parse-context} block under its
+ * own name through {@link ParseContextConfig#getConfig}.
+ * <p>
+ * Every parser, detector, renderer and translator is self-configuring, but
most never read
+ * such a block. A {@code parse-context} entry naming a self-configuring
component without
+ * this annotation (in a tika-server request, a gRPC {@code
parse_context_json}, a pipes
+ * {@code FetchEmitTuple}, a preset or the config file's {@code
parse-context}) has no effect
+ * and is logged as a warning; Tika 5.0 will refuse it.
+ * <p>
+ * A component that reads its block must carry this annotation. For Tika's own
components a
+ * missing annotation fails an assertion on every {@link
ParseContextConfig#getConfig} call,
+ * block or not, so it surfaces in any {@code -ea} test that reaches that call
(a component
+ * that calls it only when a block is present needs a test that sends one);
any other
+ * component reading a block without it is logged as a warning, once.
+ * <p>
+ * Not inherited: a subclass registered under its own name reads that name's
block only if it
+ * says so, so it carries the annotation itself.
+ *
+ * @since Apache Tika 4.3
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface RequestConfigurable {
+}
diff --git
a/tika-parsers/tika-parsers-ml/tika-parser-tess4j-module/src/main/java/org/apache/tika/parser/ocr/tess4j/Tess4JParser.java
b/tika-parsers/tika-parsers-ml/tika-parser-tess4j-module/src/main/java/org/apache/tika/parser/ocr/tess4j/Tess4JParser.java
index 68b1d64d33..a3db4d0d25 100644
---
a/tika-parsers/tika-parsers-ml/tika-parser-tess4j-module/src/main/java/org/apache/tika/parser/ocr/tess4j/Tess4JParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-parser-tess4j-module/src/main/java/org/apache/tika/parser/ocr/tess4j/Tess4JParser.java
@@ -47,6 +47,7 @@ import org.apache.tika.config.Initializable;
import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.ParseContextConfig;
import org.apache.tika.config.ParseTimeout;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.exception.TikaTimeoutException;
@@ -87,6 +88,7 @@ import org.apache.tika.utils.StringUtils;
*
* @since Apache Tika 4.0
*/
+@RequestConfigurable
@TikaComponent(name = "tess4j-parser")
public class Tess4JParser implements Parser, Initializable, TextRecognizer {
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/ClaudeVLMParser.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/ClaudeVLMParser.java
index d617c7fed0..c2dfcd1369 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/ClaudeVLMParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/ClaudeVLMParser.java
@@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
@@ -52,6 +53,7 @@ import org.apache.tika.utils.StringUtils;
*
* @since Apache Tika 4.0
*/
+@RequestConfigurable
@TikaComponent(spi = false)
public class ClaudeVLMParser extends AbstractVLMParser {
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/GeminiVLMParser.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/GeminiVLMParser.java
index c7a47c3def..c7072a06e6 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/GeminiVLMParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/GeminiVLMParser.java
@@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
@@ -54,6 +55,7 @@ import org.apache.tika.utils.StringUtils;
*
* @since Apache Tika 4.0
*/
+@RequestConfigurable
@TikaComponent(spi = false)
public class GeminiVLMParser extends AbstractVLMParser {
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
index bb05d608fc..3e368f86a4 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/OpenAIVLMParser.java
@@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
@@ -60,6 +61,7 @@ import org.apache.tika.utils.StringUtils;
* @since Apache Tika 4.0
*/
// spi = false: VLM parsers are selected by name in config, never
auto-registered (TIKA-4871)
+@RequestConfigurable
@TikaComponent(name = "openai-vlm-parser", spi = false)
public class OpenAIVLMParser extends AbstractVLMParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/executable/ExecutableParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/executable/ExecutableParser.java
index 9b3b37cdd1..20fc42680f 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/executable/ExecutableParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/executable/ExecutableParser.java
@@ -33,6 +33,7 @@ import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.EndianUtils;
import org.apache.tika.io.TikaInputStream;
@@ -48,6 +49,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
/**
* Parser for executable files. Currently supports ELF and PE
*/
+@RequestConfigurable
@TikaComponent
public class ExecutableParser implements Parser, MachineMetadata {
/**
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
index 664a288901..d005df9f76 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
@@ -30,6 +30,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.exception.TikaMemoryLimitException;
import org.apache.tika.io.EndianUtils;
@@ -45,6 +46,7 @@ import org.apache.tika.parser.ParseContext;
* Documentation on the file format is available from
* http://bellard.org/bpg/bpg_spec.txt
*/
+@RequestConfigurable
@TikaComponent
public class BPGParser extends AbstractImageParser {
protected static final int EXTENSION_TAG_EXIF = 1;
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/Cr3Parser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/Cr3Parser.java
index bb711441e1..e0e0bd498a 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/Cr3Parser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/Cr3Parser.java
@@ -43,6 +43,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -76,6 +77,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
* The {@code THMB} and {@code PRVW} records carry width and height and the
JPEG
* length ahead of the JPEG, which starts 24 bytes into the record.
*/
+@RequestConfigurable
@TikaComponent(name = "cr3-parser")
public class Cr3Parser implements Parser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
index 757c1927c6..6c83371e3b 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
@@ -31,6 +31,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.TemporaryResources;
import org.apache.tika.io.TikaInputStream;
@@ -39,6 +40,7 @@ import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
+@RequestConfigurable
@TikaComponent
public class HeifParser extends AbstractImageParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
index 151fa1e287..d93a02c8dc 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
@@ -27,6 +27,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.CacheMemoryBudget;
import org.apache.tika.io.TemporaryResources;
@@ -35,6 +36,7 @@ import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
+@RequestConfigurable
@TikaComponent
public class JpegParser extends AbstractImageParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
index da58f555f7..8806180a51 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
@@ -45,6 +45,7 @@ import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.OperatorOnly;
import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -90,6 +91,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
* ones are {@link TikaCoreProperties.EmbeddedResourceType#INLINE} images
* (TIKA-4851).
*/
+@RequestConfigurable
@TikaComponent
public class RawTiffParser extends TiffParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
index 056f36dcae..4debd693e6 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
@@ -27,6 +27,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.CacheMemoryBudget;
import org.apache.tika.io.TemporaryResources;
@@ -35,6 +36,7 @@ import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
+@RequestConfigurable
@TikaComponent
public class TiffParser extends AbstractImageParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
index e4890576fa..f1490b5500 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
@@ -26,6 +26,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.CacheMemoryBudget;
import org.apache.tika.io.TikaInputStream;
@@ -35,6 +36,7 @@ import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.XHTMLContentHandler;
+@RequestConfigurable
@TikaComponent
public class WebPParser implements Parser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
index 09d3bec35e..71061f1487 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
@@ -37,6 +37,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -76,6 +77,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
* raster preview of a vector thumbnail such as the docProps thumbnail of a
* Word document.
*/
+@RequestConfigurable
@TikaComponent
public class EMFParser extends AbstractMetafileParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
index 6a1f2e210d..af926c8e9c 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
@@ -34,6 +34,7 @@ import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
@@ -56,6 +57,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
* {@link TikaCoreProperties.EmbeddedResourceType#RENDERING} embedded document,
* the way the PDF parser emits page renderings.
*/
+@RequestConfigurable
@TikaComponent
public class WMFParser extends AbstractMetafileParser {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java
index c5855b0cbf..8639b88a8b 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java
@@ -63,6 +63,7 @@ import org.apache.tika.config.Initializable;
import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.ParseContextConfig;
import org.apache.tika.config.ParseTimeout;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.exception.TikaTimeoutException;
@@ -96,6 +97,7 @@ import org.apache.tika.utils.XMLReaderUtils;
* parseContext.set(TesseractOCRConfig.class, config);<br>
* </p>
*/
+@RequestConfigurable
// name pinned: the documented "text-recognizers" selector for this engine
@TikaComponent(name = "tesseract-ocr-parser")
public class TesseractOCRParser extends AbstractExternalProcessParser
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java
index 121f489236..725d420745 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java
@@ -59,6 +59,7 @@ import org.apache.tika.annotation.TikaComponent;
import org.apache.tika.config.ConfigDeserializer;
import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.exception.AccessPermissionException;
import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.exception.TikaException;
@@ -124,6 +125,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
* If your PDFs contain marked content or tags, consider
* {@link PDFParserConfig#setMarkedContent(MarkedContentConfig)}
*/
+@RequestConfigurable
@TikaComponent
public class PDFParser implements Parser, RenderingParser, EnrichingParser {
diff --git
a/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
b/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
index af19e4c34f..2a8ff8c4e0 100644
---
a/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
+++
b/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.tika.config.ExceptionReporting;
import org.apache.tika.config.OperatorOnly;
+import org.apache.tika.config.RequestConfigurable;
import org.apache.tika.config.loader.ComponentInfo;
import org.apache.tika.config.loader.ComponentRegistry;
import org.apache.tika.detect.Detector;
@@ -334,6 +335,22 @@ public final class ComponentNameResolver {
return false;
}
+ /**
+ * False for a self-configuring component that does not read a {@code
parse-context} block
+ * under its own name (see {@link RequestConfigurable}); true for
everything else.
+ */
+ public static boolean readsParseContextConfig(ComponentInfo info) {
+ return !info.selfConfiguring()
+ ||
info.componentClass().isAnnotationPresent(RequestConfigurable.class);
+ }
+
+ public static String notRequestConfigurableMessage(String name, Class<?>
clazz) {
+ return "Component '" + name + "' (" + clazz.getName() + ") does not
read a per-request "
+ + "or parse-context block, so this block has no effect; Tika
5.0 will refuse it. "
+ + "Configure the component in the Tika config's component
section instead. If it "
+ + "is a custom component that reads this block, annotate it
@RequestConfigurable.";
+ }
+
/** The refusal for a class {@link #isWireBlockedClass} blocks. */
public static String wireBlockedClassMessage(String name, Class<?> clazz) {
return "Component '" + name + "' (" + clazz.getName() + ") may not be
supplied via a "
diff --git
a/tika-serialization/src/main/java/org/apache/tika/serialization/ConfigDeserializer.java
b/tika-serialization/src/main/java/org/apache/tika/serialization/ConfigDeserializer.java
index 75f12777a6..b18c99d450 100644
---
a/tika-serialization/src/main/java/org/apache/tika/serialization/ConfigDeserializer.java
+++
b/tika-serialization/src/main/java/org/apache/tika/serialization/ConfigDeserializer.java
@@ -17,10 +17,17 @@
package org.apache.tika.serialization;
import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.apache.tika.config.JsonConfig;
+import org.apache.tika.config.RequestConfigurable;
+import org.apache.tika.config.loader.ComponentInfo;
import org.apache.tika.config.loader.JsonMergeUtils;
import org.apache.tika.config.loader.TikaObjectMapperFactory;
import org.apache.tika.parser.ParseContext;
@@ -52,6 +59,11 @@ import org.apache.tika.parser.ParseContext;
*/
public class ConfigDeserializer {
+ private static final Logger LOG =
LoggerFactory.getLogger(ConfigDeserializer.class);
+
+ // bounded by the component registry
+ private static final Set<String> WARNED_UNDECLARED =
ConcurrentHashMap.newKeySet();
+
private static final ObjectMapper MAPPER =
TikaObjectMapperFactory.getMapper();
/**
@@ -82,6 +94,8 @@ public class ConfigDeserializer {
if (context == null) {
return defaultConfig;
}
+ // every call, block or not, so a Tika reader's own -ea tests catch a
missing annotation
+ assert declaredOrCustom(configKey) : undeclaredMessage(configKey);
// Check resolved cache first
T resolved = context.getResolvedConfig(configKey, configClass);
@@ -95,6 +109,7 @@ public class ConfigDeserializer {
return defaultConfig;
}
+ warnIfUndeclaredCustom(configKey);
if (!jsonConfig.trusted()) {
OperatorOnlyGuard.check(MAPPER, configKey, jsonConfig.json(),
configClass);
}
@@ -111,6 +126,39 @@ public class ConfigDeserializer {
return config;
}
+ /**
+ * An undeclared reader gets a false "has no effect" warning. Tika's own
components fail an
+ * assertion (tests run with -ea); a custom one gets a warning, once, so
upgrading does not
+ * break its tests.
+ */
+ private static boolean declaredOrCustom(String configKey) {
+ return undeclaredReader(configKey)
+ .map(c -> !c.getName().startsWith("org.apache.tika."))
+ .orElse(true);
+ }
+
+ private static void warnIfUndeclaredCustom(String configKey) {
+ undeclaredReader(configKey).ifPresent(c -> {
+ if (!c.getName().startsWith("org.apache.tika.") &&
WARNED_UNDECLARED.add(c.getName())) {
+ LOG.warn("{}; add the annotation, Tika 5.0 will refuse the
block otherwise",
+ undeclaredMessage(configKey));
+ }
+ });
+ }
+
+ /** The component named {@code configKey} if it is self-configuring and
not annotated. */
+ private static Optional<Class<?>> undeclaredReader(String configKey) {
+ return ComponentNameResolver.getComponentInfo(configKey)
+ .filter(info ->
!ComponentNameResolver.readsParseContextConfig(info))
+ .map(ComponentInfo::componentClass);
+ }
+
+ private static String undeclaredMessage(String configKey) {
+ return undeclaredReader(configKey).map(Class::getName).orElse("?") + "
reads its '"
+ + configKey + "' parse-context block but is not annotated @"
+ + RequestConfigurable.class.getSimpleName();
+ }
+
/**
* Retrieves and deserializes a configuration from ParseContext.
* This version does not merge with any default config.
diff --git
a/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/ParseContextDeserializer.java
b/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/ParseContextDeserializer.java
index c4b85479e8..98fe4217a4 100644
---
a/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/ParseContextDeserializer.java
+++
b/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/ParseContextDeserializer.java
@@ -23,6 +23,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
@@ -46,6 +48,9 @@ public class ParseContextDeserializer extends
JsonDeserializer<ParseContext> {
private static final Logger LOG =
LoggerFactory.getLogger(ParseContextDeserializer.class);
+ // bounded by the component registry: unregistered names never get here
+ private static final Set<String> WARNED_UNREAD =
ConcurrentHashMap.newKeySet();
+
/**
* When true, refuses context-key types blocked from untrusted wire input
* (see {@link ComponentNameResolver#isWireBlocked(Class)}). Use for
request bodies (e.g.
@@ -114,6 +119,7 @@ public class ParseContextDeserializer extends
JsonDeserializer<ParseContext> {
while (fieldNames.hasNext()) {
String name = fieldNames.next();
JsonNode value = contextNode.get(name);
+ warnIfUnread(name);
checkForDuplicateContextKey(name, seenContextKeys);
// Store as a lazy JSON config (plain mapper: the main one may be
binary/Smile).
parseContext.setJsonConfig(name,
plainMapper().writeValueAsString(value), !restricted);
@@ -122,6 +128,23 @@ public class ParseContextDeserializer extends
JsonDeserializer<ParseContext> {
return parseContext;
}
+ /**
+ * Warns, once per component, about a block for a self-configuring
component that never
+ * reads one: it is accepted and has no effect. To be refused in 5.0.
+ *
+ * @return true if this call logged the warning
+ */
+ static boolean warnIfUnread(String name) {
+ Optional<ComponentInfo> infoOpt =
ComponentNameResolver.getComponentInfo(name);
+ if (infoOpt.isPresent() &&
!ComponentNameResolver.readsParseContextConfig(infoOpt.get())
+ && WARNED_UNREAD.add(name)) {
+ LOG.warn(ComponentNameResolver.notRequestConfigurableMessage(name,
+ infoOpt.get().componentClass()));
+ return true;
+ }
+ return false;
+ }
+
/**
* Checks if a JSON config entry would create a duplicate context key.
* <p>
diff --git
a/tika-serialization/src/test/java/com/example/tikatest/CustomReaderTestParser.java
b/tika-serialization/src/test/java/com/example/tikatest/CustomReaderTestParser.java
new file mode 100644
index 0000000000..3710decaea
--- /dev/null
+++
b/tika-serialization/src/test/java/com/example/tikatest/CustomReaderTestParser.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.example.tikatest;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.config.loader.RequestConfigTestParser;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+
+/** A third-party parser that reads its parse-context block but predates
@RequestConfigurable. */
+@TikaComponent(name = "custom-reader-test-parser", spi = false)
+public class CustomReaderTestParser implements Parser {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return
Collections.singleton(MediaType.parse("application/test+custom-reader"));
+ }
+
+ @Override
+ public void parse(TikaInputStream stream, ContentHandler handler, Metadata
metadata,
+ ParseContext context) throws IOException, SAXException,
TikaException {
+ RequestConfigTestParser.Config config =
ParseContextConfig.getConfig(context,
+ "custom-reader-test-parser",
RequestConfigTestParser.Config.class,
+ new RequestConfigTestParser.Config());
+ metadata.set("maxItems", Integer.toString(config.getMaxItems()));
+ }
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestParser.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestParser.java
new file mode 100644
index 0000000000..b8c45f4c49
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestParser.java
@@ -0,0 +1,70 @@
+/*
+ * 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.config.loader;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.config.RequestConfigurable;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+
+/** Reads a parse-context block under its own name and reports maxItems in the
metadata. */
+@RequestConfigurable
+@TikaComponent(name = "request-config-test-parser", spi = false)
+public class RequestConfigTestParser implements Parser {
+
+ private static final long serialVersionUID = 1L;
+
+ protected String configKey() {
+ return "request-config-test-parser";
+ }
+
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return
Collections.singleton(MediaType.parse("application/test+request-config"));
+ }
+
+ @Override
+ public void parse(TikaInputStream stream, ContentHandler handler, Metadata
metadata,
+ ParseContext context) throws IOException, SAXException,
TikaException {
+ Config config = ParseContextConfig.getConfig(context, configKey(),
Config.class, new Config());
+ metadata.set("maxItems", Integer.toString(config.getMaxItems()));
+ }
+
+ public static class Config {
+ private int maxItems = 1;
+
+ public int getMaxItems() {
+ return maxItems;
+ }
+
+ public void setMaxItems(int maxItems) {
+ this.maxItems = maxItems;
+ }
+ }
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestPlainSubParser.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestPlainSubParser.java
new file mode 100644
index 0000000000..56ff8f17b0
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestPlainSubParser.java
@@ -0,0 +1,26 @@
+/*
+ * 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.config.loader;
+
+import org.apache.tika.annotation.TikaComponent;
+
+/** Keeps its superclass's config key, so a block under its own name is never
read. */
+@TikaComponent(name = "request-config-test-plain-sub-parser", spi = false)
+public class RequestConfigTestPlainSubParser extends RequestConfigTestParser {
+
+ private static final long serialVersionUID = 1L;
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestSubParser.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestSubParser.java
new file mode 100644
index 0000000000..cb226fce3a
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/RequestConfigTestSubParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.config.loader;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.RequestConfigurable;
+
+/** Reads a block under its own name, so it carries {@code
@RequestConfigurable} itself. */
+@RequestConfigurable
+@TikaComponent(name = "request-config-test-sub-parser", spi = false)
+public class RequestConfigTestSubParser extends RequestConfigTestParser {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String configKey() {
+ return "request-config-test-sub-parser";
+ }
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/UnreadWarningTestParser.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/UnreadWarningTestParser.java
new file mode 100644
index 0000000000..298ee26a0b
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/UnreadWarningTestParser.java
@@ -0,0 +1,59 @@
+/*
+ * 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.config.loader;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+
+/**
+ * Self-configuring and not {@code @RequestConfigurable}; only
UnreadBlockWarningTest names it.
+ */
+@TikaComponent(name = "unread-warning-test-parser")
+public class UnreadWarningTestParser implements Parser {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Zero-arg constructor only - no JSON config support.
+ */
+ public UnreadWarningTestParser() {
+ // No config
+ }
+
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return
Collections.singleton(MediaType.parse("application/test+unread-warning"));
+ }
+
+ @Override
+ public void parse(TikaInputStream stream, ContentHandler handler, Metadata
metadata,
+ ParseContext context) throws IOException, SAXException,
TikaException {
+ metadata.set("parser-type", "unread-warning");
+ }
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/serialization/RequestConfigurableTest.java
b/tika-serialization/src/test/java/org/apache/tika/serialization/RequestConfigurableTest.java
new file mode 100644
index 0000000000..e8260dd327
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/serialization/RequestConfigurableTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.serialization;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import com.example.tikatest.CustomReaderTestParser;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.config.loader.MinimalTestParser;
+import org.apache.tika.config.loader.RequestConfigTestParser;
+import org.apache.tika.config.loader.RequestConfigTestPlainSubParser;
+import org.apache.tika.config.loader.RequestConfigTestSubParser;
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.apache.tika.serialization.serdes.ParseContextDeserializer;
+
+/** Which self-configuring components read a parse-context block of their own.
*/
+public class RequestConfigurableTest {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testAcceptsBlockNobodyReads(boolean restricted) throws
Exception {
+ // warned about, not refused, until 5.0
+ ParseContext context = ParseContextDeserializer.readParseContext(
+ json("{\"minimal-test-parser\":{}}"), restricted);
+ assertTrue(context.hasJsonConfig("minimal-test-parser"));
+ }
+
+ @Test
+ public void testOnlyAnnotatedSelfConfiguringComponentsRead() {
+ assertFalse(readsBlock("minimal-test-parser"));
+ assertTrue(readsBlock("request-config-test-parser"));
+ assertTrue(readsBlock("request-config-test-sub-parser"));
+ // not self-configuring: resolved into the context, so always read
+ assertTrue(readsBlock("timeout-limits"));
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testAppliesBlockForReader(boolean restricted) throws Exception
{
+ ParseContext context = ParseContextDeserializer.readParseContext(
+ json("{\"request-config-test-parser\":{\"maxItems\":7}}"),
restricted);
+ assertEquals("7", parse(new RequestConfigTestParser(),
context).get("maxItems"));
+ }
+
+ @Test
+ public void testAnnotatedSubclassReadsOwnBlock() throws Exception {
+ ParseContext context = ParseContextDeserializer.readParseContext(
+ json("{\"request-config-test-sub-parser\":{\"maxItems\":3}}"),
true);
+ assertEquals("3", parse(new RequestConfigTestSubParser(),
context).get("maxItems"));
+ }
+
+ /** A subclass reading its superclass's key must not inherit the
annotation and its silence. */
+ @Test
+ public void testAnnotationNotInherited() throws Exception {
+ assertFalse(readsBlock("request-config-test-plain-sub-parser"));
+ ParseContext context = ParseContextDeserializer.readParseContext(
+
json("{\"request-config-test-plain-sub-parser\":{\"maxItems\":3}}"), true);
+ assertEquals("1", parse(new RequestConfigTestPlainSubParser(),
context).get("maxItems"));
+ }
+
+ @Test
+ public void testConfigFileParseContextAcceptsBlockNobodyReads(@TempDir
Path tmp) throws Exception {
+ Path config = Files.writeString(tmp.resolve("tika-config.json"),
+ "{\"parse-context\":{\"minimal-test-parser\":{}}}",
StandardCharsets.UTF_8);
+
assertTrue(TikaLoader.load(config).loadParseContext().hasJsonConfig("minimal-test-parser"));
+ }
+
+ @Test
+ public void testReadingWithoutAnnotationFailsUnderAssertions() {
+ assumeTrue(ConfigDeserializer.class.desiredAssertionStatus());
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("minimal-test-parser", "{}");
+ AssertionError e = assertThrows(AssertionError.class,
+ () -> ConfigDeserializer.getConfig(context,
"minimal-test-parser", MinimalTestParser.class));
+ assertTrue(e.getMessage().contains("RequestConfigurable"),
e.getMessage());
+ }
+
+ /** Without a block too, so a reader's own tests catch it whether or not
they send one. */
+ @Test
+ public void testReadingWithoutAnnotationFailsWithNoBlock() {
+ assumeTrue(ConfigDeserializer.class.desiredAssertionStatus());
+ assertThrows(AssertionError.class, () ->
ConfigDeserializer.getConfig(new ParseContext(),
+ "minimal-test-parser", MinimalTestParser.class, new
MinimalTestParser()));
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testCustomReaderWithoutAnnotationStillWorks(boolean
restricted) throws Exception {
+ // only Tika's own components fail the assertion; upgrading must not
break third-party tests
+ ParseContext context = ParseContextDeserializer.readParseContext(
+ json("{\"custom-reader-test-parser\":{\"maxItems\":4}}"),
restricted);
+ assertEquals("4", parse(new CustomReaderTestParser(),
context).get("maxItems"));
+ }
+
+ private static boolean readsBlock(String name) {
+ return ComponentNameResolver.readsParseContextConfig(
+ ComponentNameResolver.getComponentInfo(name).orElseThrow());
+ }
+
+ private static JsonNode json(String s) throws IOException {
+ return MAPPER.readTree(s);
+ }
+
+ private static Metadata parse(Parser parser, ParseContext context) throws
Exception {
+ Metadata metadata = new Metadata();
+ try (TikaInputStream tis = TikaInputStream.get(new byte[0])) {
+ parser.parse(tis, new DefaultHandler(), metadata, context);
+ }
+ return metadata;
+ }
+}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
b/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
index 5a5526b941..572d8ebe6f 100644
---
a/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
+++
b/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
@@ -530,10 +530,10 @@ public class TestParseContextSerialization {
// SelfConfiguring, so they should be allowed to coexist.
String json = """
{
- "configurable-test-parser": {
+ "request-config-test-parser": {
"maxItems": 5
},
- "minimal-test-parser": {}
+ "request-config-test-sub-parser": {}
}
""";
@@ -541,10 +541,10 @@ public class TestParseContextSerialization {
// Should NOT throw - self-configuring components skip duplicate
detection
ParseContext deserialized = mapper.readValue(json, ParseContext.class);
- assertTrue(deserialized.hasJsonConfig("configurable-test-parser"),
- "configurable-test-parser should be stored as JSON config");
- assertTrue(deserialized.hasJsonConfig("minimal-test-parser"),
- "minimal-test-parser should be stored as JSON config");
+ assertTrue(deserialized.hasJsonConfig("request-config-test-parser"),
+ "request-config-test-parser should be stored as JSON config");
+
assertTrue(deserialized.hasJsonConfig("request-config-test-sub-parser"),
+ "request-config-test-sub-parser should be stored as JSON
config");
}
/**
diff --git
a/tika-serialization/src/test/java/org/apache/tika/serialization/WireRestrictedParseContextTest.java
b/tika-serialization/src/test/java/org/apache/tika/serialization/WireRestrictedParseContextTest.java
index 0ac2733371..ce092c655c 100644
---
a/tika-serialization/src/test/java/org/apache/tika/serialization/WireRestrictedParseContextTest.java
+++
b/tika-serialization/src/test/java/org/apache/tika/serialization/WireRestrictedParseContextTest.java
@@ -122,9 +122,9 @@ public class WireRestrictedParseContextTest {
// Per-request tuning of an already-loaded self-configuring parser is
config, not
// instantiation: stored as an inert jsonConfig, never bound, so it
must be allowed
// (mirrors the real pdf-parser sortByPosition / OCR-strategy use
case).
- String json = "{\"configurable-test-parser\":{\"maxItems\":5}}";
+ String json = "{\"request-config-test-parser\":{\"maxItems\":5}}";
ParseContext ctx = restrictedMapper().readValue(json,
ParseContext.class);
- assertTrue(ctx.hasJsonConfig("configurable-test-parser"));
+ assertTrue(ctx.hasJsonConfig("request-config-test-parser"));
}
@Test
diff --git
a/tika-serialization/src/test/java/org/apache/tika/serialization/serdes/UnreadBlockWarningTest.java
b/tika-serialization/src/test/java/org/apache/tika/serialization/serdes/UnreadBlockWarningTest.java
new file mode 100644
index 0000000000..e890d95f26
--- /dev/null
+++
b/tika-serialization/src/test/java/org/apache/tika/serialization/serdes/UnreadBlockWarningTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.serialization.serdes;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class UnreadBlockWarningTest {
+
+ @Test
+ public void testWarnsOncePerComponent() {
+ // a fixture no other test sends a block for, so the first call is
this one
+
assertTrue(ParseContextDeserializer.warnIfUnread("unread-warning-test-parser"));
+
assertFalse(ParseContextDeserializer.warnIfUnread("unread-warning-test-parser"));
+
assertFalse(ParseContextDeserializer.warnIfUnread("request-config-test-parser"));
+
assertFalse(ParseContextDeserializer.warnIfUnread("no-such-component"));
+ }
+}
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
index 57365a8086..8b3ae9005f 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
@@ -290,6 +290,23 @@ public class TikaResourceTest extends CXFTestBase {
assertContains("Happy New Year 2003!", responseMsg);
}
+ @Test
+ public void testConfigForParserThatDoesNotReadItIsAccepted() throws
Exception {
+ // mp4-parser takes extractSubtitles from tika-config.json only;
warned about until 5.0
+ String configJson = "{\"mp4-parser\": {\"extractSubtitles\": false}}";
+ ContentDisposition fileCd = new ContentDisposition("form-data;
name=\"file\"; filename=\"testOCR.pdf\"");
+ Attachment fileAtt = new Attachment("file",
+
ClassLoader.getSystemResourceAsStream("test-documents/testOCR.pdf"), fileCd);
+ Attachment configAtt = new Attachment("config", "application/json",
+ new
java.io.ByteArrayInputStream(configJson.getBytes(StandardCharsets.UTF_8)));
+
+ Response response = WebClient
+ .create(endPoint + TIKA_PATH + "/config")
+ .type("multipart/form-data")
+ .post(new MultipartBody(Arrays.asList(fileAtt, configAtt)));
+ assertEquals(200, response.getStatus());
+ }
+
//TIKA-2290
@Test
public void testPDFOCRConfig() throws Exception {