Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
Release 4.1.0 - unreleased

* Pipes plugins no longer bundle their own Jackson: jackson-core, -databind
and -annotations are provided by the host (tika-serialization) and the
plugins parent pom now bans bundling them, so a mapper can cross the
plugin boundary without a second copy of the Jackson classes (seven plugin
zips shipped one). Plugin configuration JSON is parsed by one shared
mapper, PluginJson (tika-plugins-core), which rejects unknown keys,
numbers for enums and duplicate keys, and accepts
// and /* */ comments; the 33 per-plugin *Config classes use it instead of
their own bare ObjectMapper (TIKA-4840).

* tika-server and tika-async-cli now start from a config that contains
// or /* */ comments, as the configuration docs have always said they
may. The main loader accepted them; the steps that re-read the user's
Expand Down
24 changes: 22 additions & 2 deletions docs/modules/ROOT/pages/pipes/plugins/writing-a-plugin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,12 @@ public class MyFetcher extends AbstractTikaExtension implements Fetcher {
}
}
----
<1> Config arrives as a **JSON string**, and the plugin parses it with its own Jackson. Nothing
richer crosses the boundary — see <<classloading>>.
<1> Config arrives as a **JSON string**; nothing richer crosses the boundary — see
<<classloading>>. The in-tree plugins parse it with `org.apache.tika.plugins.PluginJson`
(`PluginJson.read(json, MyFetcherConfig.class)`), the host's strict mapper: comments allowed,
unknown keys, duplicate keys and numbers-for-enums rejected. Using it is optional — the
boundary is the `Fetcher`, not how it is configured — but see the Jackson rule under
<<classloading>> before deciding.

IMPORTANT: `Fetcher` implementations must be thread-safe. One instance serves every concurrent
request against that fetcher id.
Expand Down Expand Up @@ -275,6 +279,20 @@ of a plugin that loads cleanly and then behaves as if it were not there — or o
The same reasoning covers logging: leave `org.slf4j` and `org.apache.logging.log4j` to the host so
plugin logs land in the host's configuration.

Jackson is the one library with a choice, and it is either/or:

* **Host Jackson (the default, what the in-tree plugins do):** `jackson-core`, `jackson-databind`
and `jackson-annotations` `provided`, absent from `lib/`. Every Tika host carries them (via
`tika-serialization`), and you may then use `PluginJson` and other host Jackson types.
* **Your own Jackson:** only if a library you depend on needs a version the host does not ship.
Bundle it (`compile` scope), and then never touch a host Jackson object — not `PluginJson`, not
`JsonMetadataList`, nothing returning an `ObjectMapper` or `JsonNode`. Your `ObjectMapper` and
the host's are different `Class` objects, and the first assignment between them fails with a
`LinkageError` or `ClassCastException`. Parse `ExtensionConfig.json()` with your own mapper.

Never both. The in-tree plugins' parent pom enforces the first choice; a third-party plugin
choosing the second must not inherit that rule.

Everything else — your own transitive libraries — belongs in `lib/`.

== Installing and configuring
Expand Down Expand Up @@ -367,6 +385,8 @@ java -Dtika.plugin.dev.mode=true ...
* `tika-core`, `tika-pipes-api`, `tika-plugins-core`, `tika-serialization`,
`tika-pipes-core`, `tika-pipes-iterator-commons`, `pf4j` and the logging
implementations `provided`, and absent from `lib/`.
* Jackson either `provided` (and then `PluginJson` is yours to use) or bundled (and then no host
Jackson type is) — never a mix.
* The zip — not an unpacked directory — dropped in a `plugin-roots` directory.
* `getName()` unique against every other loaded plugin.

Expand Down
23 changes: 22 additions & 1 deletion tika-pipes/tika-pipes-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,23 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- host-supplied via tika-serialization: a plugin that bundles its own Jackson gets a
second ObjectMapper class, and any mapper crossing the plugin boundary fails to link -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<!-- logging is host-supplied -->
<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -169,6 +186,7 @@
<excludes>
<exclude>org.slf4j</exclude>
<exclude>org.apache.logging.log4j</exclude>
<exclude>com.fasterxml.jackson.core</exclude>
</excludes>
<!-- the artifacts this module declares provided; anything else in these
groups needs a provided declaration here before it may be depended on -->
Expand All @@ -178,8 +196,11 @@
<include>org.apache.logging.log4j:log4j-api</include>
<include>org.apache.logging.log4j:log4j-core</include>
<include>org.apache.logging.log4j:log4j-slf4j2-impl</include>
<include>com.fasterxml.jackson.core:jackson-core</include>
<include>com.fasterxml.jackson.core:jackson-databind</include>
<include>com.fasterxml.jackson.core:jackson-annotations</include>
</includes>
<message>logging is host-supplied: add a provided-scope declaration in tika-pipes-plugins/pom.xml (and to this rule's include list) rather than bundling this artifact in the plugin zip</message>
<message>logging and Jackson are host-supplied: add a provided-scope declaration in tika-pipes-plugins/pom.xml (and to this rule's include list) rather than bundling this artifact in the plugin zip. A plugin whose SDK genuinely needs its own Jackson may bundle it (compile scope, skip this execution in its pom) but must then use no host Jackson type -- not PluginJson -- since the two ObjectMapper classes cannot be assigned to each other.</message>
</bannedDependencies>
</rules>
<fail>true</fail>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,14 @@
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.plugins.PluginJson;

public class AtlassianJwtFetcherConfig {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static AtlassianJwtFetcherConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json, AtlassianJwtFetcherConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse AtlassianJwtFetcherConfig from JSON", e);
}
return PluginJson.read(json, AtlassianJwtFetcherConfig.class);
}

private Integer maxConnectionsPerRoute = 1000;
Expand Down
4 changes: 0 additions & 4 deletions tika-pipes/tika-pipes-plugins/tika-pipes-az-blob/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
package org.apache.tika.pipes.emitter.azblob;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.plugins.PluginJson;

public record AZBlobEmitterConfig(
String sasToken,
Expand All @@ -31,16 +30,9 @@ public record AZBlobEmitterConfig(
@JsonProperty(defaultValue = "false") boolean overwriteExisting
) {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static AZBlobEmitterConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json, AZBlobEmitterConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse AZBlobEmitterConfig from JSON", e);
}
return PluginJson.read(json, AZBlobEmitterConfig.class);
}

public void validate() throws TikaConfigException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,14 @@
*/
package org.apache.tika.pipes.fetcher.azblob.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.plugins.PluginJson;

public class AZBlobFetcherConfig {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static AZBlobFetcherConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json, AZBlobFetcherConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse AZBlobFetcherConfig from JSON", e);
}
return PluginJson.read(json, AZBlobFetcherConfig.class);
}

private boolean spoolToTemp = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,15 @@

import java.util.Objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.pipes.pipesiterator.PipesIteratorConfig;
import org.apache.tika.plugins.PluginJson;

public class AZBlobPipesIteratorConfig extends PipesIteratorConfig {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static AZBlobPipesIteratorConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json,
AZBlobPipesIteratorConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse AZBlobPipesIteratorConfig from JSON", e);
}
return PluginJson.read(json, AZBlobPipesIteratorConfig.class);
}

private String sasToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,15 @@
import java.nio.file.Path;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.pipes.pipesiterator.PipesIteratorConfig;
import org.apache.tika.plugins.PluginJson;

public class CSVPipesIteratorConfig extends PipesIteratorConfig {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static CSVPipesIteratorConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json,
CSVPipesIteratorConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse CSVPipesIteratorConfig from JSON", e);
}
return PluginJson.read(json, CSVPipesIteratorConfig.class);
}

private Path csvPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
*/
package org.apache.tika.pipes.emitter.es;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.plugins.PluginJson;

/**
* Configuration for the ES emitter.
Expand Down Expand Up @@ -49,16 +47,9 @@ public enum UpdateStrategy {
OVERWRITE, UPSERT
}

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static ESEmitterConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json, ESEmitterConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse ESEmitterConfig from JSON", e);
}
return PluginJson.read(json, ESEmitterConfig.class);
}

/** Overrides the record default to prevent {@code apiKey} leaking into logs. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,18 @@

import java.util.Set;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.metadata.ReservedNamespaces;
import org.apache.tika.pipes.emitter.es.HttpClientConfig;
import org.apache.tika.plugins.PluginJson;
import org.apache.tika.utils.StringUtils;

public record ESReporterConfig(String esUrl, Set<String> includes, Set<String> excludes,
String keyPrefix, boolean includeRouting,
String apiKey, HttpClientConfig httpClientConfig) {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static ESReporterConfig load(final String json) throws TikaConfigException {
ESReporterConfig config;
try {
config = OBJECT_MAPPER.readValue(json, ESReporterConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse ESReporterConfig from JSON", e);
}
ESReporterConfig config = PluginJson.read(json, ESReporterConfig.class);
// keyPrefix is prepended to this reporter's own scratch-Metadata keys (parse_status/
// parse_time_ms/exit_value); reject a reserved prefix here, before it fails every report() call.
if (!StringUtils.isBlank(config.keyPrefix()) && ReservedNamespaces.isTikaNative(config.keyPrefix())) {
Expand Down
5 changes: 0 additions & 5 deletions tika-pipes/tika-pipes-plugins/tika-pipes-file-system/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
*/
package org.apache.tika.pipes.emitter.fs;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.plugins.PluginJson;

public record FileSystemEmitterConfig(String basePath, String fileExtension, ON_EXISTS onExists, boolean prettyPrint, boolean allowAbsolutePaths) {

Expand All @@ -34,17 +32,9 @@ enum ON_EXISTS {
}
}

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static FileSystemEmitterConfig load(final String json)
throws TikaConfigException {
try {
return OBJECT_MAPPER.readValue(json,
FileSystemEmitterConfig.class);
} catch (JsonProcessingException e) {
throw new TikaConfigException(
"Failed to parse FileSystemEmitterConfig from JSON", e);
}
return PluginJson.read(json, FileSystemEmitterConfig.class);
}

}
Loading
Loading