blob: a5adc3e0e58c0bff52560b3ca1caf3672544f502 [file]
// 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.
= Serialization and Configuration
Tika 4.x uses JSON-based configuration and serialization throughout the system.
This document explains how the serialization system works and how to create
components that integrate with it.
== Overview
Tika's serialization system provides:
* **JSON Configuration**: Configure Tika components using JSON files
* **Friendly Names**: Reference components by name (e.g., `pdf-parser`) instead of class names
* **ParseContext Serialization**: Send per-request configuration via `FetchEmitTuple`
* **Security**: Only registered components can be instantiated from JSON
The system is built on Jackson with custom serializers/deserializers in the
`tika-serialization` module.
== JSON Configuration Format
Tika uses a compact format for component configuration:
[source,json]
----
{
"auto-detect-parser": {
"throwOnZeroBytes": false
},
"parse-context": {
"commons-digester-factory": {
"digests": [
{ "algorithm": "MD5" },
{ "algorithm": "SHA256" }
]
}
}
}
----
Components can be specified as:
* **String**: `"pdf-parser"` - creates instance with defaults
* **Object**: `{"pdf-parser": {"ocr": {"strategy": "AUTO"}}}` - creates configured instance
== The @TikaComponent Annotation
The `@TikaComponent` annotation is required for any class that should be
configurable via JSON. It serves multiple purposes:
1. **Registration**: Registers the class with a friendly name
2. **Index Generation**: Creates lookup files for name-to-class resolution
3. **SPI Registration**: Optionally registers for Java ServiceLoader
4. **Security**: Acts as an allowlist for deserialization
=== Basic Usage
[source,java]
----
@TikaComponent
public class MyCustomParser implements Parser {
// Parser implementation
}
----
This automatically:
* Generates friendly name `my-custom-parser` from the class name
* Adds to `META-INF/tika/parsers.idx` for name lookup
* Adds to `META-INF/services/org.apache.tika.parser.Parser` for SPI
=== Annotation Attributes
[cols="1,1,3"]
|===
| Attribute | Default | Description
| `name`
| (auto-generated)
| Custom friendly name instead of deriving from class name
| `spi`
| `true`
| Whether to register in `META-INF/services/` for ServiceLoader
| `contextKey`
| (auto-detected)
| Class to use as ParseContext key (rarely needed)
| `defaultFor`
| (none)
| Marks as default implementation for an interface
|===
=== Example with Attributes
[source,java]
----
@TikaComponent(name = "my-parser", spi = false)
public class MyInternalParser implements Parser {
// Not auto-discovered via SPI, but configurable via JSON
}
----
== Context Key Detection
When storing components in `ParseContext`, Tika needs to know which class
to use as the lookup key. For example, `CommonsDigesterFactory` should be
retrievable via `parseContext.get(DigesterFactory.class)`.
=== Automatic Detection
Tika automatically detects the context key by checking if your class implements
one of these known interfaces:
* `Parser`, `Detector`, `EncodingDetector`
* `MetadataFilter`, `Translator`, `Renderer`
* `DigesterFactory`, `ContentHandlerFactory`
* `EmbeddedDocumentExtractorFactory`, `MetadataWriteLimiterFactory`
[source,java]
----
@TikaComponent
public class CommonsDigesterFactory implements DigesterFactory {
// Context key automatically detected as DigesterFactory.class
}
----
=== Explicit Context Key
For interfaces not in the auto-detection list, specify explicitly:
[source,java]
----
@TikaComponent(contextKey = DocumentSelector.class)
public class SkipEmbeddedDocumentSelector implements DocumentSelector { }
----
== Service Interface Categories
=== First-Class Service Interfaces
These are loaded via SPI and have dedicated index files:
[cols="1,1"]
|===
| Interface | Index File
| `Parser` | `parsers.idx`
| `Detector` | `detectors.idx`
| `EncodingDetector` | `encoding-detectors.idx`
| `LanguageDetector` | `language-detectors.idx`
| `Translator` | `translators.idx`
| `Renderer` | `renderers.idx`
| `MetadataFilter` | `metadata-filters.idx`
|===
=== ParseContext Components
Components not implementing first-class interfaces go to `parse-context.idx`:
* `DigesterFactory` - Digest/checksum calculation
* `ContentHandlerFactory` - SAX content handler creation
* `EmbeddedDocumentExtractorFactory` - Embedded document handling
* `MetadataWriteLimiterFactory` - Metadata write limiting
== Self-Configuring Components
Components implementing `SelfConfiguring` handle their own configuration
at runtime rather than during initial loading:
[source,java]
----
@TikaComponent
public class PDFParser extends AbstractParser implements SelfConfiguring {
private PDFParserConfig defaultConfig = new PDFParserConfig();
@Override
public void configure(ParseContext parseContext) {
PDFParserConfig config = ParseContextConfig.getConfig(
parseContext, "pdf-parser", PDFParserConfig.class, defaultConfig);
// Use config...
}
}
----
Benefits:
* Per-request configuration via `ParseContext`
* Lazy loading - config only parsed when needed
* Merging with defaults handled automatically
== ParseContext Serialization
`ParseContext` can be serialized to JSON for transmission (e.g., in `FetchEmitTuple`):
[source,json]
----
{
"parse-context": {
"pdf-parser": {
"ocr": {
"strategy": "AUTO"
},
"extractInlineImages": true
},
"commons-digester-factory": {
"digests": [{"algorithm": "SHA256"}]
}
}
}
----
=== Typed Section
For components that need immediate deserialization (not lazy loading):
[source,json]
----
{
"parse-context": {
"typed": {
"handler-config": {
"type": "XML",
"writeLimit": 100000
}
}
}
}
----
== Security Model
The serialization system implements a security allowlist:
1. **@TikaComponent Required**: Only annotated classes are registered
2. **Registry Lookup**: Deserialization only instantiates registered classes
3. **No Arbitrary Classes**: Unknown class names cause errors, not instantiation
This prevents attacks where malicious JSON specifies dangerous classes
for instantiation.
[source,java]
----
// This will FAIL - class not registered
{
"parse-context": {
"java.lang.Runtime": {} // Error: Unknown component
}
}
----
== Creating a Custom Component
Complete example of a custom metadata filter:
[source,java]
----
package com.example.tika;
import org.apache.tika.config.TikaComponent;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.filter.MetadataFilter;
@TikaComponent
public class UpperCaseFilter implements MetadataFilter {
private String fieldName = "title";
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getFieldName() {
return fieldName;
}
@Override
public void filter(Metadata metadata) throws TikaException {
String value = metadata.get(fieldName);
if (value != null) {
metadata.set(fieldName, value.toUpperCase());
}
}
}
----
Configure in JSON. Metadata filters are loaded via `parse-context` (they
implement the `MetadataFilter` interface, which is a `ParseContext`-keyed
component):
[source,json]
----
{
"parse-context": {
"upper-case-filter": {"fieldName": "dc:title"}
}
}
----
Or with defaults (string form constructs with no overrides):
[source,json]
----
{
"parse-context": {
"upper-case-filter": {}
}
}
----
== Troubleshooting
=== "Unknown component name" Error
* Ensure class has `@TikaComponent` annotation
* Verify annotation processing ran during compilation
* Check that `META-INF/tika/*.idx` file exists in JAR
=== Component Not Found in ParseContext
* Verify you're using the correct interface type for lookup
* Check if explicit `contextKey` is needed
* For self-configuring components, ensure `configure()` was called
=== SPI Not Loading Component
* Check that `spi = true` (the default)
* Verify `META-INF/services/` file exists
* Ensure JAR is on classpath