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 c18bc4dd2e TIKA-4884: cleanups for TIKA-4872 (#3143)
c18bc4dd2e is described below
commit c18bc4dd2ec53080690db8b8f90a32eae3fbfc2f
Author: Tim Allison <[email protected]>
AuthorDate: Tue Sep 8 16:23:56 2026 -0400
TIKA-4884: cleanups for TIKA-4872 (#3143)
---
CHANGES.txt | 11 +++
docs/modules/ROOT/pages/configuration/index.adoc | 6 +-
.../pages/configuration/parsers/vlm-parsers.adoc | 4 +
.../pages/migration-to-4x/chunk-strategies.adoc | 6 +-
.../inference-handler-requirements.adoc | 4 +
.../parser/enricher/CompositeContentEnricher.java | 21 +++++-
.../tika/parser/enricher/ContentEnrichersTest.java | 16 ++++
.../org/apache/tika/parser/mock}/MockEnricher.java | 4 +-
.../org/apache/tika/inference/ChunkSerializer.java | 2 +-
.../apache/tika/parser/vlm/AbstractVLMParser.java | 14 +++-
.../org/apache/tika/parser/vlm/VLMOCRConfig.java | 2 +-
.../resources/config-examples/pdf-parser-full.json | 2 +-
.../apache/tika/pipes/core/PipesClientTest.java | 1 +
.../tika/config/loader/ContentEnricherLoader.java | 20 +++--
.../config/loader/ContentEnricherLoaderTest.java | 48 ++++++++++++
.../tika/config/loader/TestLegacyOcrEnricher.java | 25 ++-----
.../org/apache/tika/server/core/CXFTestBase.java | 9 ++-
.../server/standard/ContentEnricherWireTest.java | 86 ++++++++++++++++++++++
.../configs/tika-config-content-enrichers.json | 11 +++
19 files changed, 255 insertions(+), 37 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 64bfc36c74..673a175fe8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,16 @@
Release 4.1.0 - unreleased
+ * "content-enrichers": _mime-exclude now matches the real media type, so
+ excluding image/tiff also drops a legacy image/ocr-tiff engine (it was
+ a no-op). The zero-media-types check at config load asks the engine
+ itself, so a _mime-include list no longer masks an unreachable one.
+ VLM parsers implement Closeable and release their HTTP client
+ (TIKA-4884).
+
+ * tika-inference and tika-vlm are experimental: classes, config keys and
+ metadata output may change in minor releases without deprecation or
+ compatibility shims (TIKA-4884).
+
* A missing OOXML relationship target no longer aborts the whole file:
the threaded-comment and person lookups in xlsx and the page lookups in
vsdx went straight to POI's getRelatedPart, whose unchecked
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc
b/docs/modules/ROOT/pages/configuration/index.adoc
index dc90e9fee9..76a807b913 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -144,12 +144,14 @@ the engine explicitly instead of relying on which OCR
module happens to be on th
An enricher advertises its *real* media types (`image/png`, ...) and does not
compete with the
parser registered for those types: `image-parser` still parses the image and
calls the enricher.
(The bundled OCR engines still advertise legacy `image/ocr-*` types; those are
mapped to the
-real type, so naming them here just works.) *Every* enricher matching a media
type runs, in the
+real type, so naming them here just works, and an entry's
`_mime-include`/`_mime-exclude` name
+real types too: `image/tiff`, not `image/ocr-tiff`.) *Every* enricher matching
a media type runs, in the
order listed — so an OCR engine followed by a VLM that tags images is two
entries, both invoked
per image. The list is authoritative: a media type no configured enricher
matches gets no
enrichment — never a classpath engine you did not name — and a named engine
that reports no
media types at startup (missing native binary, unreachable inference server)
fails config load
-rather than going silently inert. Failures are best-effort: one enricher
failing does not stop
+rather than going silently inert; that check is made against the engine
itself, so a
+`_mime-include` list cannot stand in for an unreachable engine. Failures are
best-effort: one enricher failing does not stop
the others, and every failure is still reported through the parser's normal
exception handling
(timeouts abort the chain immediately). With no `content-enrichers`
configured, behavior is
unchanged — whichever OCR engine is on the classpath is used, exactly as
before, and a WARN is
diff --git a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
index 65b55f7daf..a74d4bb70b 100644
--- a/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
+++ b/docs/modules/ROOT/pages/configuration/parsers/vlm-parsers.adoc
@@ -22,6 +22,10 @@ to remote Vision-Language Model (VLM) endpoints. These
parsers send images
(or PDFs) to an external API and convert the model's markdown response into
structured XHTML.
+NOTE: `tika-vlm` is experimental. Its classes, configuration keys and metadata
output may
+change in minor releases without a deprecation cycle, and no compatibility
shims are kept
+for earlier shapes.
+
Three implementations are provided out of the box. None is auto-loaded: each
must be
named explicitly in your configuration. (Changed in 4.1.0: `openai-vlm-parser`
previously
auto-registered via SPI.) To use a VLM as the OCR engine for embedded images
and rendered
diff --git a/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
b/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
index 18d9e4c76e..1acbc366db 100644
--- a/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
+++ b/docs/modules/ROOT/pages/migration-to-4x/chunk-strategies.adoc
@@ -22,6 +22,10 @@ Tika 4.x introduces a unified chunking and embedding
pipeline (`tika-inference`)
`tk:chunks` array on each document's metadata. This page describes how those
chunks reach
Elasticsearch and OpenSearch, and why the shipped strategy is the one it is.
+NOTE: `tika-inference` is experimental. Its classes, configuration keys and
the `tk:chunks`
+layout may change in minor releases without a deprecation cycle, and no
compatibility shims
+are kept for earlier shapes.
+
== The `tk:chunks` Field
All chunk data lands in one metadata field, `tk:chunks`, as a JSON array.
There is no
@@ -52,7 +56,7 @@ A single chunk looks like:
----
{
"text": "Revenue grew 15% year-over-year...",
- "vector": "base64-encoded-float32-le",
+ "vector": "base64-encoded-float32-be",
"locators": {
"text": [{"start_offset": 0, "end_offset": 120}],
"paginated": [{"page": 1}]
diff --git
a/docs/modules/ROOT/pages/migration-to-4x/inference-handler-requirements.adoc
b/docs/modules/ROOT/pages/migration-to-4x/inference-handler-requirements.adoc
index 5e56bc9931..6bd4ccc521 100644
---
a/docs/modules/ROOT/pages/migration-to-4x/inference-handler-requirements.adoc
+++
b/docs/modules/ROOT/pages/migration-to-4x/inference-handler-requirements.adoc
@@ -23,6 +23,10 @@ metadata field, splits it with the `MarkdownChunker`, and
sends the resulting ch
embeddings endpoint. The chunker splits on markdown structure, so the content
handler that
produced `tk:content` has to be `MARKDOWN` for it to do its job.
+NOTE: `tika-inference` is experimental. Its classes, configuration keys and
the `tk:chunks`
+layout may change in minor releases without a deprecation cycle, and no
compatibility shims
+are kept for earlier shapes.
+
Since 4.x that is the default, so the common case needs no configuration. This
page records
why, and what happens if you override it.
diff --git
a/tika-core/src/main/java/org/apache/tika/parser/enricher/CompositeContentEnricher.java
b/tika-core/src/main/java/org/apache/tika/parser/enricher/CompositeContentEnricher.java
index 398b3fb225..e26587516b 100644
---
a/tika-core/src/main/java/org/apache/tika/parser/enricher/CompositeContentEnricher.java
+++
b/tika-core/src/main/java/org/apache/tika/parser/enricher/CompositeContentEnricher.java
@@ -20,6 +20,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -27,6 +28,7 @@ import java.util.Set;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.ParserDecorator;
/**
* Media-type-keyed registry of content enrichers: ordinary {@link Parser}s
that a container
@@ -36,7 +38,8 @@ import org.apache.tika.parser.Parser;
* <p>
* Members advertise their <em>real</em> media types ({@code image/png});
legacy engines
* still advertising the {@code image/ocr-*} pseudo-types are keyed under the
real type, so
- * they are nameable here unmodified. An enricher does not compete with the
parser
+ * they are nameable here unmodified and a {@code _mime-exclude} on one
matches the real type
+ * too. An enricher does not compete with the parser
* registered for the same type: that parser still runs and calls the enricher.
*
* @since Apache Tika 4.1
@@ -51,9 +54,13 @@ public class CompositeContentEnricher implements
Serializable {
Map<MediaType, List<Parser>> tmp = new HashMap<>();
ParseContext empty = new ParseContext();
for (Parser enricher : enrichers) {
+ Set<MediaType> excluded = excludedRealTypes(enricher);
for (MediaType mediaType : enricher.getSupportedTypes(empty)) {
// legacy engines advertise image/ocr-*; key under the real
type
MediaType keyType =
stripLegacyOcrPrefix(mediaType.getBaseType());
+ if (excluded.contains(keyType)) {
+ continue;
+ }
List<Parser> forType = tmp.computeIfAbsent(keyType, k -> new
ArrayList<>());
if (!forType.contains(enricher)) {
forType.add(enricher);
@@ -64,6 +71,18 @@ public class CompositeContentEnricher implements
Serializable {
this.enricherMap = Collections.unmodifiableMap(tmp);
}
+ // decorator excludes are literal; "image/tiff" must also drop a legacy
"image/ocr-tiff"
+ private static Set<MediaType> excludedRealTypes(Parser enricher) {
+ if (!(enricher instanceof ParserDecorator.MimeFilteringDecorator
decorator)) {
+ return Collections.emptySet();
+ }
+ Set<MediaType> excluded = new HashSet<>();
+ for (MediaType excludeType : decorator.getExcludeTypes()) {
+ excluded.add(stripLegacyOcrPrefix(excludeType.getBaseType()));
+ }
+ return excluded;
+ }
+
private static MediaType stripLegacyOcrPrefix(MediaType mediaType) {
String subtype = mediaType.getSubtype();
if (subtype.startsWith(LegacyDispatchEnricher.OCR_MEDIATYPE_PREFIX)) {
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 416e08993e..53635f8e49 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
@@ -19,6 +19,7 @@ package org.apache.tika.parser.enricher;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.Collections;
@@ -38,6 +39,7 @@ import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.ParserDecorator;
public class ContentEnrichersTest {
@@ -93,6 +95,20 @@ public class ContentEnrichersTest {
assertNull(explicit.overrideSeenDuringParse);
}
+ @Test
+ public void testExcludeAppliesToLegacyPseudoType() {
+ // either spelling of the excluded type must work
+ Set<MediaType> legacy = Set.of(OCR_PNG, MediaType.image("ocr-tiff"));
+ for (String spelling : new String[]{"image/tiff", "image/ocr-tiff"}) {
+ Parser excluded = ParserDecorator.withoutTypes(new
RecordingParser(legacy),
+ Collections.singleton(MediaType.parse(spelling)));
+ CompositeContentEnricher enrichers =
+ new CompositeContentEnricher(List.of(excluded));
+ assertEquals(Set.of(PNG), enrichers.getSupportedTypes(), spelling);
+
assertTrue(enrichers.getEnrichers(MediaType.image("tiff")).isEmpty(), spelling);
+ }
+ }
+
@Test
public void testLegacyFallbackMintsAndRestores() throws Exception {
RecordingParser composite = new
RecordingParser(Collections.singleton(OCR_PNG));
diff --git
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
b/tika-core/src/test/java/org/apache/tika/parser/mock/MockEnricher.java
similarity index 93%
copy from
tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
copy to tika-core/src/test/java/org/apache/tika/parser/mock/MockEnricher.java
index 73b499b270..924b3ba132 100644
---
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
+++ b/tika-core/src/test/java/org/apache/tika/parser/mock/MockEnricher.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.tika.pipes.core;
+package org.apache.tika.parser.mock;
import java.util.Collections;
import java.util.Set;
@@ -30,7 +30,7 @@ import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.XHTMLContentHandler;
-/** Fixture: proves the invocation and config path with no OCR binary
installed. */
+/** Fixture for {@code "content-enrichers"}: nameable as {@code
mock-enricher}, no OCR binary. */
@TikaComponent(name = "mock-enricher", spi = false)
public class MockEnricher implements Parser {
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ChunkSerializer.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ChunkSerializer.java
index 6a8f66bea0..2119532c65 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ChunkSerializer.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/ChunkSerializer.java
@@ -34,7 +34,7 @@ import org.apache.tika.metadata.TikaCoreProperties;
/**
* Serializes and deserializes a list of {@link Chunk} objects to/from JSON.
- * Vectors are stored as base64-encoded little-endian float32 via
+ * Vectors are stored as base64-encoded big-endian float32 via
* {@link VectorSerializer}. Locators are nested under a {@code "locators"}
* object with optional {@code text}, {@code paginated}, {@code spatial},
* and {@code temporal} arrays.
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/AbstractVLMParser.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/AbstractVLMParser.java
index 1c4bd89011..5c46a4748c 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/AbstractVLMParser.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/AbstractVLMParser.java
@@ -18,6 +18,7 @@ package org.apache.tika.parser.vlm;
import static org.apache.tika.sax.XHTMLContentHandler.XHTML;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
@@ -62,7 +63,7 @@ import org.apache.tika.sax.XHTMLContentHandler;
*
* @since Apache Tika 4.0
*/
-public abstract class AbstractVLMParser implements Parser, Initializable {
+public abstract class AbstractVLMParser implements Parser, Initializable,
Closeable {
private static final long serialVersionUID = 1L;
@@ -227,7 +228,9 @@ public abstract class AbstractVLMParser implements Parser,
Initializable {
@Override
public void initialize() throws TikaConfigException {
- this.httpClient = buildHttpClient();
+ if (httpClient == null) {
+ httpClient = buildHttpClient();
+ }
String healthUrl = getHealthCheckUrl(defaultConfig);
if (healthUrl == null) {
// No health check configured (e.g. Claude) — assume available
@@ -252,6 +255,13 @@ public abstract class AbstractVLMParser implements Parser,
Initializable {
}
}
+ @Override
+ public void close() throws IOException {
+ if (httpClient != null) {
+ httpClient.close();
+ }
+ }
+
// ---- shared helpers ---------------------------------------------------
protected VLMOCRConfig getConfig(ParseContext parseContext)
diff --git
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
index b3f3a3c80e..7a01f8d042 100644
---
a/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
+++
b/tika-parsers/tika-parsers-ml/tika-vlm/src/main/java/org/apache/tika/parser/vlm/VLMOCRConfig.java
@@ -22,7 +22,7 @@ import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.utils.StringUtils;
/**
- * Configuration for {@link VLMOCRParser}.
+ * Configuration for {@link AbstractVLMParser} and its subclasses.
* <p>
* The parser expects an OpenAI-compatible chat completions endpoint
* (e.g. from vLLM, Ollama, or a local FastAPI server). The image is
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/pdf-parser-full.json
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/pdf-parser-full.json
index 19aca7fd22..8754565dd7 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/pdf-parser-full.json
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/resources/config-examples/pdf-parser-full.json
@@ -62,7 +62,7 @@
// Options: PNG, TIFF, JPEG
"imageFormat": "PNG",
// Image quality (0.0-1.0) for lossy formats.
- "imageQuality": 1.0,
+ "imageQuality": 0.5,
// Rendered-image color model.
// Options: RGB, GRAY
"imageType": "GRAY",
diff --git
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/PipesClientTest.java
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/PipesClientTest.java
index 8233f939eb..d4d55fe941 100644
---
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/PipesClientTest.java
+++
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/PipesClientTest.java
@@ -45,6 +45,7 @@ import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.mock.MockEnricher;
import org.apache.tika.pipes.api.FetchEmitTuple;
import org.apache.tika.pipes.api.ParseMode;
import org.apache.tika.pipes.api.PipesResult;
diff --git
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ContentEnricherLoader.java
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ContentEnricherLoader.java
index 781b3feb93..5f5540a546 100644
---
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ContentEnricherLoader.java
+++
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ContentEnricherLoader.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.ParserDecorator;
import org.apache.tika.parser.enricher.CompositeContentEnricher;
/**
@@ -54,16 +55,23 @@ class ContentEnricherLoader implements
ComponentLoader<CompositeContentEnricher>
throw new TikaConfigException(
"Failed to load content enricher: " + entry.getKey(),
e);
}
- // this type snapshot lasts the life of the process, so an engine
reporting
- // nothing (missing binary, dead server) must fail load, not go
silently inert
- if (enricher.getSupportedTypes(empty).isEmpty()) {
+ // lifetime snapshot: an empty engine must fail load, not go
inert; ask the
+ // engine itself, since a _mime-include answers for the decorator
+ if (unwrap(enricher).getSupportedTypes(empty).isEmpty()) {
throw new TikaConfigException("Content enricher \"" +
entry.getKey()
- + "\" advertises no media types. Is the engine
unavailable "
- + "(missing native binary, unreachable inference
server) or "
- + "configured to skip enrichment?");
+ + "\" advertises no media types (a _mime-include list
does not "
+ + "count). Is the engine unavailable (missing native
binary, "
+ + "unreachable inference server) or configured to skip
enrichment?");
}
enrichers.add(enricher);
}
return new CompositeContentEnricher(enrichers);
}
+
+ private static Parser unwrap(Parser parser) {
+ while (parser instanceof ParserDecorator decorator) {
+ parser = decorator.getWrappedParser();
+ }
+ return parser;
+ }
}
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
index 49acce1070..0f7490104c 100644
---
a/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/ContentEnricherLoaderTest.java
@@ -26,6 +26,8 @@ import java.nio.file.Path;
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.apache.tika.mime.MediaType;
import org.apache.tika.parser.CompositeParser;
@@ -83,6 +85,52 @@ public class ContentEnricherLoaderTest {
"unexpected message: " + e.getMessage());
}
+ @ParameterizedTest
+ @ValueSource(strings = {"image/tiff", "image/ocr-tiff"})
+ public void testMimeExcludeReachesLegacyPseudoType(String excluded) throws
Exception {
+ TikaLoader loader = load("""
+ {
+ "content-enrichers": [
+ {"test-legacy-ocr-enricher": {"_mime-exclude":
["EXCLUDED"]}}
+ ]
+ }
+ """.replace("EXCLUDED", excluded));
+ CompositeContentEnricher enrichers =
loader.get(CompositeContentEnricher.class);
+ assertEquals(java.util.Set.of(MediaType.image("png")),
enrichers.getSupportedTypes());
+ assertTrue(enrichers.getEnrichers(MediaType.image("tiff")).isEmpty());
+ assertEquals(1, enrichers.getEnrichers(MediaType.image("png")).size());
+ }
+
+ @Test
+ public void testMimeIncludeDoesNotMaskUnavailableEngine() throws Exception
{
+ TikaLoader loader = load("""
+ {
+ "content-enrichers": [
+ {"test-unavailable-enricher": {"_mime-include":
["image/png"]}}
+ ]
+ }
+ """);
+ org.apache.tika.exception.TikaConfigException e =
+ org.junit.jupiter.api.Assertions.assertThrows(
+ org.apache.tika.exception.TikaConfigException.class,
+ () -> loader.get(CompositeContentEnricher.class));
+ assertTrue(e.getMessage().contains("advertises no media types"),
+ "unexpected message: " + e.getMessage());
+ }
+
+ @Test
+ public void testMimeIncludeNarrowsLegacyEngine() throws Exception {
+ TikaLoader loader = load("""
+ {
+ "content-enrichers": [
+ {"test-legacy-ocr-enricher": {"_mime-include":
["image/png"]}}
+ ]
+ }
+ """);
+ CompositeContentEnricher enrichers =
loader.get(CompositeContentEnricher.class);
+ assertEquals(java.util.Set.of(MediaType.image("png")),
enrichers.getSupportedTypes());
+ }
+
@Test
public void testNoContentEnrichersConfigured() throws Exception {
TikaLoader loader = load("""
diff --git
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/TestLegacyOcrEnricher.java
similarity index 58%
rename from
tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
rename to
tika-serialization/src/test/java/org/apache/tika/config/loader/TestLegacyOcrEnricher.java
index 73b499b270..fa331cae43 100644
---
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/MockEnricher.java
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/TestLegacyOcrEnricher.java
@@ -14,13 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.tika.pipes.core;
+package org.apache.tika.config.loader;
-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.io.TikaInputStream;
@@ -28,29 +26,20 @@ import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
-import org.apache.tika.sax.XHTMLContentHandler;
-/** Fixture: proves the invocation and config path with no OCR binary
installed. */
-@TikaComponent(name = "mock-enricher", spi = false)
-public class MockEnricher implements Parser {
+/** Fixture: an OCR engine still advertising {@code image/ocr-*} pseudo-types.
*/
+@TikaComponent(name = "test-legacy-ocr-enricher", spi = false)
+public class TestLegacyOcrEnricher implements Parser {
private static final long serialVersionUID = 1L;
- public static final String MARKER_KEY = "mock-enricher";
- public static final String MARKER_TEXT = "MOCK-ENRICHED-TEXT";
-
@Override
public Set<MediaType> getSupportedTypes(ParseContext context) {
- return Collections.singleton(MediaType.image("png"));
+ return Set.of(MediaType.image("ocr-png"), MediaType.image("ocr-tiff"));
}
@Override
- public void parse(TikaInputStream tis, ContentHandler handler, Metadata
metadata,
- ParseContext context) throws SAXException {
- metadata.set(MARKER_KEY, "ENRICHED");
- XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
- xhtml.startDocument();
- xhtml.characters(MARKER_TEXT);
- xhtml.endDocument();
+ public void parse(TikaInputStream stream, ContentHandler handler, Metadata
metadata,
+ ParseContext context) {
}
}
diff --git
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
index 155b7dcc87..32b57813d1 100644
---
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
+++
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
@@ -306,8 +306,8 @@ public abstract class CXFTestBase {
}
/**
- * Creates a default test config with pipes configuration.
- * If the tika config contains metadata-filters, they are merged into the
pipes config.
+ * Creates a default test config with pipes configuration, carrying
parse-context,
+ * metadata-filters, presets and content-enrichers over from the tika
config.
*
* @param tikaConfigPath path to the tika config (may contain
metadata-filters)
*
@@ -357,6 +357,11 @@ public abstract class CXFTestBase {
if (metadataFilters != null && !metadataFilters.isEmpty()) {
root.set("metadata-filters", metadataFilters);
}
+ // the worker loads enrichers from its own config
+ JsonNode contentEnrichers = tikaConfig.get("content-enrichers");
+ if (contentEnrichers != null && !contentEnrichers.isEmpty()) {
+ root.set("content-enrichers", contentEnrichers);
+ }
// The worker resolves preset names from its own config, so
presets must be
// visible there just like the parse-context defaults above.
JsonNode presets = tikaConfig.get("presets");
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ContentEnricherWireTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ContentEnricherWireTest.java
new file mode 100644
index 0000000000..bc2fe4d868
--- /dev/null
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ContentEnricherWireTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.standard;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+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.lifecycle.SingletonResourceProvider;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.mock.MockEnricher;
+import org.apache.tika.serialization.JsonMetadataList;
+import org.apache.tika.server.core.CXFTestBase;
+import org.apache.tika.server.core.resource.RecursiveMetadataResource;
+import org.apache.tika.server.core.writer.MetadataListMessageBodyWriter;
+
+/** A config-named enricher reaches the forked worker and its output comes
back (TIKA-4872). */
+public class ContentEnricherWireTest extends CXFTestBase {
+
+ private static final String META_PATH = "/rmeta";
+
+ @Override
+ protected void setUpResources(JAXRSServerFactoryBean sf) {
+ sf.setResourceClasses(RecursiveMetadataResource.class);
+ sf.setResourceProvider(RecursiveMetadataResource.class,
+ new SingletonResourceProvider(new
RecursiveMetadataResource(tikaResource)));
+ }
+
+ @Override
+ protected void setUpProviders(JAXRSServerFactoryBean sf) {
+ List<Object> providers = new ArrayList<>();
+ providers.add(new MetadataListMessageBodyWriter());
+ sf.setProviders(providers);
+ }
+
+ @Override
+ protected InputStream getTikaConfigInputStream() {
+ return
getClass().getResourceAsStream("/configs/tika-config-content-enrichers.json");
+ }
+
+ @Test
+ public void testEnricherOutputSurvivesFork() throws Exception {
+ ByteArrayOutputStream png = new ByteArrayOutputStream();
+ ImageIO.write(new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB),
"png", png);
+
+ Response response = WebClient
+ .create(endPoint + META_PATH)
+ .accept("application/json")
+ .put(png.toByteArray());
+
+ Reader reader = new InputStreamReader((InputStream)
response.getEntity(), UTF_8);
+ List<Metadata> metadataList = JsonMetadataList.fromJson(reader);
+ assertEquals(1, metadataList.size());
+ Metadata metadata = metadataList.get(0);
+ assertEquals("ENRICHED", metadata.get(MockEnricher.MARKER_KEY));
+ assertContains(MockEnricher.MARKER_TEXT,
metadata.get(TikaCoreProperties.TIKA_CONTENT));
+ }
+}
diff --git
a/tika-server/tika-server-standard/src/test/resources/configs/tika-config-content-enrichers.json
b/tika-server/tika-server-standard/src/test/resources/configs/tika-config-content-enrichers.json
new file mode 100644
index 0000000000..d2be254675
--- /dev/null
+++
b/tika-server/tika-server-standard/src/test/resources/configs/tika-config-content-enrichers.json
@@ -0,0 +1,11 @@
+{
+ "content-enrichers": [ { "mock-enricher": {} } ],
+ "auto-detect-parser": {
+ "throwOnZeroBytes": false
+ },
+ "parse-context": {
+ "timeout-limits": {
+ "progressTimeoutMillis": 30000
+ }
+ }
+}