Skip to content

Commit 21cf116

Browse files
committed
2 parents 87c2558 + 80275fe commit 21cf116

44 files changed

Lines changed: 2453 additions & 37 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<module>tika-ml</module>
4646
<module>tika-encoding-detectors</module>
4747
<module>tika-parsers</module>
48+
<module>tika-metadata-schema</module>
4849
<module>tika-bundles</module>
4950
<module>tika-xmp</module>
5051
<module>tika-langdetect</module>

tika-core/src/main/java/org/apache/tika/metadata/DWG.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public interface DWG {
2323

2424
String DWG_PREFIX = "dwg" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
2525

26+
PassthroughPrefix RAW_FIELD =
27+
PassthroughPrefix.file(DWG_PREFIX, "DWGRead JSON header/summary field names");
28+
2629
Property APPLICATION_NAME = Property.externalText(DWG_PREFIX + "applicationName");
2730

2831
Property APPLICATION_VERSION = Property.externalText(DWG_PREFIX + "applicationVersion");

tika-core/src/main/java/org/apache/tika/metadata/HTML.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
public interface HTML {
2020
String PREFIX_HTML_META = "html" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
2121

22+
PassthroughPrefix SCRAPED_META = PassthroughPrefix.file(PREFIX_HTML_META,
23+
"scraped <meta>/http-equiv/OpenGraph names not mapped to a Property");
24+
2225

2326
/**
2427
* If a script element contains a src value, this value

tika-core/src/main/java/org/apache/tika/metadata/MAPI.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public interface MAPI {
2727
String PREFIX_MAPI_META = "mapi" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
2828
String PREFIX_MAPI_ATTACH_META = "mapi:attach" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
2929
String PREFIX_MAPI_PROPERTY = PREFIX_MAPI_META + "property" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
30+
PassthroughPrefix PROPERTY = PassthroughPrefix.file(PREFIX_MAPI_PROPERTY, "MAPI named properties");
3031

3132
/**
3233
* MAPI message class. What type of .msg/MAPI file is it?

tika-core/src/main/java/org/apache/tika/metadata/Message.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public interface Message {
2727
String MESSAGE_RAW_HEADER_PREFIX =
2828
MESSAGE_PREFIX + "Raw-Header" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
2929

30+
PassthroughPrefix RAW_HEADER = PassthroughPrefix.file(MESSAGE_RAW_HEADER_PREFIX,
31+
"RFC822 / Outlook raw email header names");
32+
3033
String MESSAGE_RECIPIENT_ADDRESS = "Message-Recipient-Address";
3134

3235
String MESSAGE_FROM = "Message-From";

tika-core/src/main/java/org/apache/tika/metadata/Office.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public interface Office {
3737
*/
3838
String USER_DEFINED_METADATA_NAME_PREFIX = "custom:";
3939

40+
PassthroughPrefix USER_DEFINED = PassthroughPrefix.file(USER_DEFINED_METADATA_NAME_PREFIX,
41+
"OOXML/OLE2/ODF user-defined document properties");
42+
4043

4144
/**
4245
* Keywords pertaining to a document. Also populates {@link DublinCore#SUBJECT}.

tika-core/src/main/java/org/apache/tika/metadata/PDF.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public interface PDF {
5151
String PDF_DOC_INFO_CUSTOM_PREFIX =
5252
PDF_DOC_INFO_PREFIX + "custom" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
5353

54+
PassthroughPrefix DOC_INFO_CUSTOM =
55+
PassthroughPrefix.file(PDF_DOC_INFO_CUSTOM_PREFIX, "PDF Info-dict custom keys");
56+
5457
Property DOC_INFO_CREATED = Property.internalDate(PDF_DOC_INFO_PREFIX + "created");
5558

5659
Property DOC_INFO_CREATOR = Property.internalText(PDF_DOC_INFO_PREFIX + "creator");
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.tika.metadata;
18+
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
24+
/**
25+
* A Tika-owned prefix under which keys are passed through from the source: the prefix is fixed, but
26+
* each key name comes verbatim from the document or tool, so keys are unbounded and can't be
27+
* {@link Property} constants. Declaring one self-registers it, so the open set is enumerable (as
28+
* {@link Property} makes the closed set) and lintable: a String write is legitimate iff its key is a
29+
* registered {@link Property} or its prefix is a registered {@code PassthroughPrefix}.
30+
*
31+
* @since Apache Tika 4.0.0
32+
*/
33+
public final class PassthroughPrefix {
34+
35+
public enum Provenance { FILE, TOOL }
36+
37+
private static final Map<String, PassthroughPrefix> REGISTRY = new ConcurrentHashMap<>();
38+
39+
private final String prefix;
40+
private final Provenance provenance;
41+
private final String description;
42+
43+
private PassthroughPrefix(String prefix, Provenance provenance, String description) {
44+
this.prefix = prefix;
45+
this.provenance = provenance;
46+
this.description = description;
47+
REGISTRY.put(prefix, this);
48+
}
49+
50+
public static PassthroughPrefix file(String prefix, String description) {
51+
return new PassthroughPrefix(prefix, Provenance.FILE, description);
52+
}
53+
54+
public static PassthroughPrefix tool(String prefix, String description) {
55+
return new PassthroughPrefix(prefix, Provenance.TOOL, description);
56+
}
57+
58+
/** The full key for a source-derived {@code suffix}. */
59+
public String key(String suffix) {
60+
return prefix + suffix;
61+
}
62+
63+
public String prefix() {
64+
return prefix;
65+
}
66+
67+
public Provenance provenance() {
68+
return provenance;
69+
}
70+
71+
public String description() {
72+
return description;
73+
}
74+
75+
/** Declared prefixes, from loaded classes only. */
76+
public static Collection<PassthroughPrefix> registered() {
77+
return Collections.unmodifiableCollection(REGISTRY.values());
78+
}
79+
}

tika-metadata-schema/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
# tika-metadata-schema
18+
19+
A machine-readable schema of Apache Tika's metadata keys, plus a registry-driven validator
20+
(`MetadataKeyValidator`) that classifies any key as CLOSED / OPEN / TEMPLATE / UNKNOWN.
21+
22+
**Scope: `tika-core` + the standard parser bundle.** The heavier/optional parser families
23+
(scientific, sqlite3, nlp, vlm) are *not* scanned — pulling their runtime deps (netcdf, grib,
24+
opennlp, DL4J, sqlite-jdbc) into a build-time schema module isn't worth it. Their keys are the only
25+
ones absent (e.g. `sqlite3:`, `vlm:`, `grib:`, `netcdf:`, `ctakes:`, `NER_`). `MetadataCoverageTest`
26+
enforces this: any module declaring keys that is neither scanned nor on its explicit out-of-scope
27+
list fails the build, so nothing escapes *silently*.
28+
29+
## `metadata-keys.json` — the closed set (generated + gated)
30+
Every key Tika declares as a `Property` constant, plus the bounded digest cross-product
31+
(`X-TIKA:digest:<ALGORITHM>[:<ENCODING>]`, enumerated from `DigestDef`). Each record:
32+
`{ key, namespace, valueType, cardinality }`.
33+
34+
**Generated, never hand-edited.** `SchemaGenerator` scans the parser classpath for classes that
35+
declare a `Property` field, force-loads them, reads the global `Property` table, and writes stable
36+
sorted JSON. `MetadataSchemaTest` regenerates in-memory and asserts it matches the committed file, so
37+
the registry can never drift from the declarations.
38+
39+
Regenerate after adding/changing a `Property` **or** a `PassthroughPrefix` (writes both files):
40+
```
41+
java -cp <tika-metadata-schema + deps classpath> \
42+
org.apache.tika.metadata.schema.SchemaGenerator \
43+
src/main/resources/org/apache/tika/metadata/metadata-keys.json \
44+
src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json
45+
```
46+
47+
## `metadata-open-namespaces.json` — the open sets (generated + gated)
48+
The **prefixes** under which parsers mint file-controlled key names at runtime — names that are not
49+
`Property` constants, so the individual keys cannot be enumerated (scraped HTML `<meta>` under
50+
`html:`, OOXML `custom:`, email `Message:Raw-Header:`, Access `MDB_PROP:`, Vorbis comments, FLV
51+
attributes, unmapped image/XMP tags, …). Each record: `{ prefix, provenance, description }`.
52+
53+
**Generated from the `PassthroughPrefix` declarations, never hand-edited.** Every such prefix is a
54+
registered `PassthroughPrefix` constant; `SchemaGenerator` reads that registry the same way it reads
55+
the `Property` table, and `MetadataSchemaTest` gates it identically. Adding a passthrough prefix in a
56+
parser and forgetting to regenerate fails the build.
57+
58+
Not covered here: **templates** — parameterized key families like XMP `rdf:Alt` language variants
59+
`<base-key>:<lang>` (`dc:title:fr`), where the *suffix* rather than the prefix is open. These are
60+
documented by rule, not enumerated.
61+
62+
## `metadata-string-keys.json` — legacy bare-String closed keys (curated + gated)
63+
A handful of closed keys predate `Property` and are still declared as bare `String` constants
64+
(`HttpHeaders.CONTENT_TYPE` = `Content-Type`, the `Content-*`/`Location` family, `Message-*` /
65+
`Multipart-*`, `tika:chunks`). They self-register nowhere, so the `Property` scan can't see them —
66+
yet Tika emits them constantly. Each record: `{ key, source }`.
67+
68+
**Curated, but gated against the code:** `MetadataStringKeysTest` reflects each `source` constant
69+
(e.g. `HttpHeaders.CONTENT_TYPE`) and asserts its live value equals `key`, so a rename/retype/value
70+
change fails the build. The right long-term fix is to make these `Property` constants (then they'd
71+
move to `metadata-keys.json` automatically); that's a large, `Content-Type`-blast-radius change left
72+
for a future major release.
73+
74+
## `MetadataKeyValidator` — the registry-driven lint
75+
Classifies any key by reading the three registries above (no parser classes needed):
76+
`CLOSED` (in `metadata-keys.json` or `metadata-string-keys.json`), `OPEN` (under a registered
77+
passthrough prefix), `TEMPLATE` (a `<closed-key>:<lang>` lang-alt instance), or `UNKNOWN` — a typo,
78+
an unregistered namespace, or a key nobody declared. This is the payoff the registries exist for: a
79+
data-driven legitimacy check instead of a hand-coded regex.
80+
81+
Together the files describe the key space of the scanned bundle: closed keys are enumerated and
82+
gated (Property-backed and legacy-String alike); open namespaces are enumerated by prefix and gated;
83+
templates are described by rule; and `MetadataCoverageTest` guarantees no scanned-bundle module is
84+
silently missed. Keys from the out-of-scope families above are excluded by design.

tika-metadata-schema/pom.xml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one or more
4+
contributor license agreements. See the NOTICE file distributed with
5+
this work for additional information regarding copyright ownership.
6+
The ASF licenses this file to You under the Apache License, Version 2.0
7+
(the "License"); you may not use this file except in compliance with
8+
the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
<parent>
23+
<groupId>org.apache.tika</groupId>
24+
<artifactId>tika-parent</artifactId>
25+
<version>${revision}</version>
26+
<relativePath>../tika-parent/pom.xml</relativePath>
27+
</parent>
28+
29+
<artifactId>tika-metadata-schema</artifactId>
30+
<name>Apache Tika metadata schema</name>
31+
<description>Generates and enforces the machine-readable metadata key registry
32+
(metadata-keys.json) from the live Property declarations.</description>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>org.apache.tika</groupId>
37+
<artifactId>tika-core</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
<!-- brings the standard parser classes onto the classpath so their Property
41+
constants are discoverable by the generator -->
42+
<dependency>
43+
<groupId>org.apache.tika</groupId>
44+
<artifactId>tika-parsers-standard-package</artifactId>
45+
<version>${project.version}</version>
46+
<type>pom</type>
47+
</dependency>
48+
49+
<dependency>
50+
<groupId>org.junit.jupiter</groupId>
51+
<artifactId>junit-jupiter</artifactId>
52+
<scope>test</scope>
53+
</dependency>
54+
</dependencies>
55+
<!-- Regenerate the committed registry with:
56+
java -cp <module+deps classpath> org.apache.tika.metadata.schema.SchemaGenerator \
57+
src/main/resources/org/apache/tika/metadata/metadata-keys.json
58+
MetadataSchemaTest regenerates in-memory and asserts no diff, so CI catches drift. -->
59+
</project>

0 commit comments

Comments
 (0)