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 23e6877e58 TIKA-4889: per-request text-recognizers switch and the
recipes page (#3160)
23e6877e58 is described below
commit 23e6877e58b43b881cae752250e62c2658231ee7
Author: Tim Allison <[email protected]>
AuthorDate: Mon Sep 14 19:47:26 2026 -0400
TIKA-4889: per-request text-recognizers switch and the recipes page (#3160)
---
CHANGES.txt | 7 +
docs/modules/ROOT/nav.adoc | 1 +
docs/modules/ROOT/pages/configuration/index.adoc | 8 +
.../pages/configuration/inference-recipes.adoc | 162 +++++++++++++++++++++
.../ROOT/pages/configuration/inference.adoc | 4 +-
.../ROOT/pages/using-tika/server/index.adoc | 3 +-
.../tika/parser/enricher/ContentEnrichers.java | 25 +++-
.../parser/enricher/TextRecognizerSelection.java | 44 ++++++
.../tika/parser/enricher/ContentEnrichersTest.java | 22 +++
.../apache/tika/parser/pdf/AbstractPDF2XHTML.java | 3 +-
.../org/apache/tika/parser/pdf/PDFParserTest.java | 29 ++++
.../server/standard/TextRecognizerWireTest.java | 41 ++++++
12 files changed, 344 insertions(+), 5 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 3604832d1f..b14be90515 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,12 @@
Release 4.1.0 - unreleased
+ * Per-request switch for text recognizers: {"parse-context":
+ {"text-recognizers": {"enabled": false}}} runs no recognizer or annotator
+ from the list for that parse, in every container, and OCR_ONLY no longer
+ reports a missing engine for it. With the inference switch this is
+ "everything off for one request". New docs page with the OCR and
+ inference recipes (TIKA-4889).
+
* New tika-eval-structure: a standalone tool that compares two sets of
XHTML extracts block by block (paragraph order as Kendall tau, splits and
merges, words one side glues that the other splits, artifact and untagged
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 2a08054b81..db8acecf9e 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -62,6 +62,7 @@
** xref:configuration/parsers/external-parser.adoc[External Parser (ffmpeg,
exiftool, etc.)]
** xref:configuration/parsers/tess4j-parser.adoc[Tess4J OCR (In-Process,
advanced)]
** xref:configuration/inference.adoc[Inference: Engines and Bindings]
+** xref:configuration/inference-recipes.adoc[OCR and Inference Recipes]
** xref:configuration/metadata-filters.adoc[Metadata Filters]
** xref:configuration/detectors.adoc[Detectors]
** xref:configuration/digesters.adoc[Digesters]
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc
b/docs/modules/ROOT/pages/configuration/index.adoc
index 507015ff37..d88a9310d5 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -160,6 +160,14 @@ the engine inline, still works: `{ "tesseract-ocr-parser":
{ "language": "eng" }
{ "text-recognizers": [] }
----
+*Turn it off for one request.* The list stays configured; the request switches
it off, in
+every container, and no engine is missed as an error:
+
+[source,json]
+----
+{ "parse-context": { "text-recognizers": { "enabled": false } } }
+----
+
Engine types: `tesseract-ocr-parser`, `tess4j-parser`, `openai-vlm-parser`,
`claude-vlm-parser`,
`gemini-vlm-parser`. At startup Tika logs one line per engine with the media
types it enriches, so
the effective engine is never a guess.
diff --git a/docs/modules/ROOT/pages/configuration/inference-recipes.adoc
b/docs/modules/ROOT/pages/configuration/inference-recipes.adoc
new file mode 100644
index 0000000000..2b6b15d5d5
--- /dev/null
+++ b/docs/modules/ROOT/pages/configuration/inference-recipes.adoc
@@ -0,0 +1,162 @@
+//
+// 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.
+//
+
+= OCR and Inference Recipes
+
+Each recipe is a complete config. They combine three sections and one PDF knob:
+xref:configuration/index.adoc#text-recognizers[`text-recognizers`] (what
produces text from
+pixels), xref:configuration/inference.adoc[`engines` and `inference`] (what is
called, on what),
+and `pdf-parser.inference.input` (what a PDF releases). Every default parser
stays loaded in
+all of them.
+
+== Scanned pages only
+
+OCR a PDF page only when its extracted text is poor, and every embedded image.
Tesseract is
+configured once and named as the recognizer; the PDF's `AUTO` strategy is the
default.
+
+[source,json]
+----
+{
+ "engines": { "tesseract": { "tesseract-ocr-parser": { "language": "eng" } }
},
+ "text-recognizers": [ { "engine": "tesseract" } ]
+}
+----
+
+== Text vectors for every document
+
+One embedding request per document tree, chunks of the container and its
attachments mixed,
+each chunk on its own document with a `text` locator.
+
+[source,json]
+----
+{
+ "engines": {
+ "embedder": { "openai-embedding-engine": { "baseUrl": "http://embed:8000",
"model": "bge-m3" } }
+ },
+ "inference": [
+ { "id": "text-vectors", "engine": "embedder", "input": "TEXT", "tasks":
["embed"],
+ "chunker": { "markdown-chunker": { "maxChunkChars": 1500,
"overlapChars": 200 } } }
+ ]
+}
+----
+
+== Page vectors, no OCR
+
+Every PDF page rendered once for the embedding engine, no text recognizer
anywhere. The render
+settings live under `ocr` and are sized here for an image model rather than
Tesseract.
+
+[source,json]
+----
+{
+ "engines": {
+ "clip": { "openai-embedding-engine": { "baseUrl": "http://clip:8000",
"model": "siglip2" } }
+ },
+ "inference": [
+ { "id": "page-vectors", "engine": "clip", "input": "PAGES", "tasks":
["embed"], "maxChunks": 50 }
+ ],
+ "text-recognizers": [],
+ "parsers": [
+ { "default-parser": {} },
+ { "pdf-parser": { "inference": { "input": ["PAGES"] },
+ "ocr": { "strategy": "NO_OCR", "dpi": 100, "imageType":
"RGB" } } }
+ ]
+}
+----
+
+== OCR when needed and a vector on every page
+
+One render per page feeds both: Tesseract on the pages the `AUTO` verdict
picks, the embedding
+engine on all of them. The settings serve both consumers, so keep the dpi
Tesseract needs.
+
+[source,json]
+----
+{
+ "engines": {
+ "tesseract": { "tesseract-ocr-parser": { "language": "eng" } },
+ "clip": { "openai-embedding-engine": { "baseUrl": "http://clip:8000",
"model": "siglip2" } }
+ },
+ "text-recognizers": [ { "engine": "tesseract" } ],
+ "inference": [
+ { "id": "page-vectors", "engine": "clip", "input": "PAGES", "tasks":
["embed"] }
+ ],
+ "parsers": [
+ { "default-parser": {} },
+ { "pdf-parser": { "inference": { "input": ["PAGES"] }, "ocr": {
"imageType": "RGB" } } }
+ ]
+}
+----
+
+== PDFs by page vectors, everything else by text vectors
+
+Two bindings. The PDF releases its pages, records that it did, and the text
stage skips it;
+every other document is embedded by its text. List `["TEXT", "PAGES"]` on the
PDF to get both.
+
+[source,json]
+----
+{
+ "engines": {
+ "embedder": { "openai-embedding-engine": { "baseUrl": "http://embed:8000",
"model": "bge-m3" } },
+ "clip": { "openai-embedding-engine": { "baseUrl": "http://clip:8000",
"model": "siglip2" } }
+ },
+ "inference": [
+ { "id": "text-vectors", "engine": "embedder", "input": "TEXT", "tasks":
["embed"],
+ "chunker": { "markdown-chunker": { "maxChunkChars": 1500,
"overlapChars": 200 } } },
+ { "id": "page-vectors", "engine": "clip", "input": "PAGES", "tasks":
["embed"], "maxChunks": 50 }
+ ],
+ "parsers": [
+ { "default-parser": {} },
+ { "pdf-parser": { "inference": { "input": ["PAGES"] },
+ "ocr": { "strategy": "NO_OCR", "dpi": 100, "imageType":
"RGB" } } }
+ ]
+}
+----
+
+== OCR off for PDFs only
+
+The recognizer stays on for embedded and standalone images; the PDF parser
never renders.
+
+[source,json]
+----
+{
+ "engines": { "tesseract": { "tesseract-ocr-parser": {} } },
+ "text-recognizers": [ { "engine": "tesseract" } ],
+ "parsers": [
+ { "default-parser": {} },
+ { "pdf-parser": { "ocr": { "strategy": "NO_OCR" } } }
+ ]
+}
+----
+
+== Everything off for one request
+
+Both lists stay configured; the request switches them off. Wire-safe, so it
works as a
+xref:using-tika/server/index.adoc[preset], on `/rmeta/config`, and on a pipes
tuple.
+
+[source,json]
+----
+{ "parse-context": { "text-recognizers": { "enabled": false },
+ "inference": { "enabled": false } } }
+----
+
+`inference.bindings` selects a subset instead of all-or-nothing; a PDF can be
switched to pages
+for one request with `"pdf-parser": { "inference": { "input": ["PAGES"] } }`
in the same block.
+
+== Not yet
+
+A caption or tag per picture (a chat task on a chat engine), OCR configured
but promoted per
+request, and separate render settings for OCR and for an image model are 4.2
work; the
+xref:configuration/inference.adoc[Inference] page says what each binding can
do today.
diff --git a/docs/modules/ROOT/pages/configuration/inference.adoc
b/docs/modules/ROOT/pages/configuration/inference.adoc
index a4439cde7a..fd2a982ee8 100644
--- a/docs/modules/ROOT/pages/configuration/inference.adoc
+++ b/docs/modules/ROOT/pages/configuration/inference.adoc
@@ -202,7 +202,9 @@ bindings.
----
`enabled: false` runs nothing for this request; `bindings` names the ones that
run, every
-enabled binding when absent. Names only, so it is wire-safe and works as a
+enabled binding when absent. The recognizers have the same switch,
+`{"text-recognizers": {"enabled": false}}`; together they are "everything off
for one
+request" (see xref:configuration/inference-recipes.adoc[Recipes]). Names only,
so it is wire-safe and works as a
xref:using-tika/server/index.adoc[preset]. A name not in the configured list
fails the request.
Engines cannot be defined or changed per request.
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index 3ad3f58cc1..5c914e6692 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -132,7 +132,8 @@ the catalog fails startup:
{
"presets": {
"some-catalog-preset": true,
- "no-ocr": { "pdf-parser": { "ocr": { "strategy": "NO_OCR" } } }
+ "no-ocr": { "pdf-parser": { "ocr": { "strategy": "NO_OCR" } } },
+ "no-enrichment": { "text-recognizers": { "enabled": false }, "inference":
{ "enabled": false } }
}
}
----
diff --git
a/tika-core/src/main/java/org/apache/tika/parser/enricher/ContentEnrichers.java
b/tika-core/src/main/java/org/apache/tika/parser/enricher/ContentEnrichers.java
index b1a8329312..74db09a17f 100644
---
a/tika-core/src/main/java/org/apache/tika/parser/enricher/ContentEnrichers.java
+++
b/tika-core/src/main/java/org/apache/tika/parser/enricher/ContentEnrichers.java
@@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.exception.TikaTimeoutException;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -116,7 +118,8 @@ public final class ContentEnrichers {
private static Parser select(CompositeContentEnricher enrichers, MediaType
mediaType,
ParseContext context) {
- if (mediaType == null || isActive(context) || isSuspended(context)) {
+ if (mediaType == null || isActive(context) || isSuspended(context)
+ || isDisabled(context)) {
return null;
}
boolean annotatorsOnly = isRecognizersSuspended(context);
@@ -160,7 +163,7 @@ public final class ContentEnrichers {
ParseContext context) {
Objects.requireNonNull(target, "target");
if (mediaType == null || isActive(context) || isSuspended(context)
- || isRecognizersSuspended(context)) {
+ || isRecognizersSuspended(context) || isDisabled(context)) {
return false;
}
if (enrichers != null) {
@@ -262,6 +265,24 @@ public final class ContentEnrichers {
}
}
+ private static final TextRecognizerSelection ENABLED = new
TextRecognizerSelection();
+
+ /** Whether the request switched the list off: {@code {"text-recognizers":
{"enabled": false}}}. */
+ public static boolean isDisabled(ParseContext context) {
+ TextRecognizerSelection selection =
context.get(TextRecognizerSelection.class);
+ if (selection == null) {
+ try {
+ selection = ParseContextConfig.getConfig(context,
"text-recognizers",
+ TextRecognizerSelection.class, ENABLED);
+ } catch (TikaConfigException | IOException e) {
+ // the request's parse-context was validated before the parse;
keep enriching
+ LOG.warn("invalid \"text-recognizers\" in parse-context;
recognizers stay on", e);
+ return false;
+ }
+ }
+ return !selection.isEnabled();
+ }
+
private static boolean isActive(ParseContext context) {
ActiveEnrichment active = context.get(ActiveEnrichment.class);
return active != null && active.active;
diff --git
a/tika-core/src/main/java/org/apache/tika/parser/enricher/TextRecognizerSelection.java
b/tika-core/src/main/java/org/apache/tika/parser/enricher/TextRecognizerSelection.java
new file mode 100644
index 0000000000..0d448b0cb5
--- /dev/null
+++
b/tika-core/src/main/java/org/apache/tika/parser/enricher/TextRecognizerSelection.java
@@ -0,0 +1,44 @@
+/*
+ * 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.parser.enricher;
+
+import java.io.Serializable;
+
+import org.apache.tika.annotation.TikaComponent;
+
+/**
+ * The per-request switch for the {@code "text-recognizers"} list:
+ * {@code {"parse-context": {"text-recognizers": {"enabled": false}}}} runs no
recognizer or
+ * annotator from the list for this parse, in every container. Wire-safe: it
names nothing.
+ *
+ * @since Apache Tika 4.1
+ */
+@TikaComponent(name = "text-recognizers", spi = false)
+public class TextRecognizerSelection implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean enabled = true;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+}
diff --git
a/tika-core/src/test/java/org/apache/tika/parser/enricher/ContentEnrichersTest.java
b/tika-core/src/test/java/org/apache/tika/parser/enricher/ContentEnrichersTest.java
index 98f0cf109c..d9c138f1ec 100644
---
a/tika-core/src/test/java/org/apache/tika/parser/enricher/ContentEnrichersTest.java
+++
b/tika-core/src/test/java/org/apache/tika/parser/enricher/ContentEnrichersTest.java
@@ -185,6 +185,28 @@ public class ContentEnrichersTest {
assertNotNull(ContentEnrichers.get(null, PNG, target(PNG), context));
}
+ /** The request's switch turns the whole list off; the list itself is
untouched. */
+ @Test
+ public void testDisabledPerRequest() throws Exception {
+ RecognizingParser recognizer = new RecognizingParser(Set.of(PNG),
true);
+ RecordingParser annotator = new RecordingParser(Set.of(PNG));
+ CompositeContentEnricher list = listOf(recognizer, annotator);
+ ParseContext off = new ParseContext();
+ TextRecognizerSelection selection = new TextRecognizerSelection();
+ selection.setEnabled(false);
+ off.set(TextRecognizerSelection.class, selection);
+ assertTrue(ContentEnrichers.isDisabled(off));
+ assertNull(ContentEnrichers.get(list, PNG, target(PNG), off));
+ assertFalse(ContentEnrichers.hasTextRecognizer(list, PNG, target(PNG),
off));
+ off.set(Parser.class, compositeOf(new RecognizingParser(Set.of(PNG),
true)));
+ assertNull(ContentEnrichers.get(null, PNG, target(PNG), off),
"discovery is off too");
+
+ ParseContext on = new ParseContext();
+ assertFalse(ContentEnrichers.isDisabled(on));
+ assertNotNull(ContentEnrichers.get(list, PNG, target(PNG), on));
+ assertTrue(ContentEnrichers.hasTextRecognizer(list, PNG, target(PNG),
on));
+ }
+
@Test
public void testSuspendedDispatch() throws Exception {
ParseContext context = new ParseContext();
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
index 155df57869..566359a6e2 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
@@ -715,7 +715,8 @@ class AbstractPDF2XHTML extends PDFTextStripper {
return PageOcr.SKIPPED;
}
if (ocrEngine == null) {
- if (ocrStrategy == OCR_ONLY || ocrStrategy ==
OCR_AND_TEXT_EXTRACTION) {
+ if ((ocrStrategy == OCR_ONLY || ocrStrategy ==
OCR_AND_TEXT_EXTRACTION)
+ && !ContentEnrichers.isDisabled(context)) {
throw new TikaException(
"I regret that I couldn't find an OCR engine to handle
" +
ocrImageMediaType + ". Name one that covers it
in " +
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
index 14c5349cdb..d25c4e856e 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
@@ -91,6 +91,7 @@ import
org.apache.tika.parser.enricher.CompositeContentEnricher;
import org.apache.tika.parser.enricher.ContentEnricher;
import org.apache.tika.parser.enricher.ContentEnrichers;
import org.apache.tika.parser.enricher.TextRecognizer;
+import org.apache.tika.parser.enricher.TextRecognizerSelection;
import org.apache.tika.parser.hook.ParseHook;
import org.apache.tika.parser.hook.ParseHooks;
import org.apache.tika.renderer.RenderingTracker;
@@ -2032,6 +2033,34 @@ public class PDFParserTest extends TikaTest {
assertEquals(0, tracker.getNextId() - 1, "no binding wants pages:
nothing is rendered");
}
+ /** The request's switch: no OCR, text kept, and OCR_ONLY does not
complain about an engine. */
+ @Test
+ public void testRecognizersOffPerRequest() throws Exception {
+ TextRecognizerSelection off = new TextRecognizerSelection();
+ off.setEnabled(false);
+
+ PageEnricher recognizer = new PageEnricher("MOCK_OCR_CONTENT", true);
+ PDFParser parser = new PDFParser();
+ parser.setContentEnrichers(new
CompositeContentEnricher(List.of(recognizer)));
+ ParseContext context = new ParseContext();
+ context.set(PDFParserConfig.class, autoOcrTriggeringPage16());
+ context.set(TextRecognizerSelection.class, off);
+ String xml = parsePdfToXml(parser, "testPDF_bad_page_303226.pdf", new
Metadata(), context);
+ assertEquals(0, recognizer.calls);
+ assertNotContained("MOCK_OCR_CONTENT", xml);
+ // the verdict page keeps its extracted text
+ assertContains("42936", xml);
+
+ PDFParserConfig ocrOnly = new PDFParserConfig();
+ ocrOnly.getOcr().setStrategy(OcrConfig.Strategy.OCR_ONLY);
+ context = new ParseContext();
+ context.set(PDFParserConfig.class, ocrOnly);
+ context.set(TextRecognizerSelection.class, off);
+ xml = parsePdfToXml(parser, "testPDF_bookmarks.pdf", new Metadata(),
context);
+ assertEquals(0, recognizer.calls);
+ assertNotContained("MOCK_OCR_CONTENT", xml);
+ }
+
/** Annotating enricher for image/png: writes one chunk per render, never
text. */
private static class PageAnnotator implements Parser, ContentEnricher {
private static final long serialVersionUID = 1L;
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TextRecognizerWireTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TextRecognizerWireTest.java
index 6a77ab635a..0152012d92 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TextRecognizerWireTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TextRecognizerWireTest.java
@@ -18,19 +18,26 @@ package org.apache.tika.server.standard;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.junit.jupiter.api.Test;
@@ -83,4 +90,38 @@ public class TextRecognizerWireTest extends CXFTestBase {
assertEquals("ENRICHED", metadata.get(MockEnricher.MARKER_KEY));
assertContains(MockEnricher.MARKER_TEXT,
metadata.get(TikaCoreProperties.TIKA_CONTENT));
}
+
+ /** The request's switch crosses the fork: nothing from the list runs. */
+ @Test
+ public void testRecognizersOffPerRequest() throws Exception {
+ ByteArrayOutputStream png = new ByteArrayOutputStream();
+ ImageIO.write(new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB),
"png", png);
+ String config = """
+ { "parse-context": { "text-recognizers": { "enabled": false }
} }
+ """;
+ ContentDisposition fileCd = new ContentDisposition(
+ "form-data; name=\"file\"; filename=\"image.png\"");
+ Attachment fileAtt = new Attachment("file", new
ByteArrayInputStream(png.toByteArray()),
+ fileCd);
+ Attachment configAtt = new Attachment("config", "application/json",
+ new ByteArrayInputStream(config.getBytes(UTF_8)));
+
+ Response response = WebClient
+ .create(endPoint + META_PATH + "/config")
+ .type("multipart/form-data")
+ .accept("application/json")
+ .post(new MultipartBody(Arrays.asList(fileAtt, configAtt)));
+
+ assertEquals(200, response.getStatus());
+ Reader reader = new InputStreamReader((InputStream)
response.getEntity(), UTF_8);
+ Metadata metadata = JsonMetadataList.fromJson(reader).get(0);
+ assertNull(metadata.get(MockEnricher.MARKER_KEY));
+
assertFalse(String.valueOf(metadata.get(TikaCoreProperties.TIKA_CONTENT))
+ .contains(MockEnricher.MARKER_TEXT));
+ }
+
+ @Override
+ protected boolean isAllowPerRequestConfig() {
+ return true;
+ }
}