You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/08/12 16:15:58 UTC
(tika) 02/07: TIKA-4816 metadata-key stage 2: mint-time reserved-name validation
This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch TIKA-4816-metadata-key-api
in repository https://gitbox.apache.org/repos/asf/tika.git
commit ff467d33c5233669837e256b2de2eda45d24b7b0
Author: tallison <ta...@apache.org>
AuthorDate: Tue Aug 11 12:29:22 2026 -0400
TIKA-4816 metadata-key stage 2: mint-time reserved-name validation
---
.../apache/tika/digest/InputStreamDigester.java | 16 +-
.../java/org/apache/tika/metadata/Property.java | 171 +++++++++++++++++++++
.../java/org/apache/tika/metadata/Rendering.java | 18 ++-
.../apache/tika/metadata/TikaCoreProperties.java | 142 ++++++++++-------
.../org/apache/tika/metadata/TikaPagedText.java | 6 +-
.../java/org/apache/tika/utils/ParserUtils.java | 7 +-
.../tika/metadata/PropertyReservedNameTest.java | 161 +++++++++++++++++++
.../org/apache/tika/eval/app/ProfilerBase.java | 7 +-
.../apache/tika/metadata/metadata-key-fields.json | 3 +
.../tika/renderer/pdf/pdfbox/PDFBoxRenderer.java | 12 +-
10 files changed, 474 insertions(+), 69 deletions(-)
diff --git a/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java b/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
index 849b54a833..842746c141 100644
--- a/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
+++ b/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.utils.StringUtils;
@@ -35,6 +36,9 @@ import org.apache.tika.utils.StringUtils;
*/
public class InputStreamDigester implements Digester {
+ private static final String DIGEST_KEY_PREFIX =
+ TikaCoreProperties.TIKA_META_PREFIX + "digest" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
+
private static final Map<String, Property> METADATA_KEY_PROPERTIES = new ConcurrentHashMap<>();
private final String algorithm;
@@ -51,10 +55,20 @@ public class InputStreamDigester implements Digester {
public InputStreamDigester(String algorithm, String metadataKey, Encoder encoder) {
this.algorithm = algorithm;
this.metadataProperty =
- METADATA_KEY_PROPERTIES.computeIfAbsent(metadataKey, Property::internalText);
+ METADATA_KEY_PROPERTIES.computeIfAbsent(metadataKey, InputStreamDigester::mintProperty);
this.encoder = encoder;
}
+ // metadataKey is config-supplied (algorithm/encoding names), not document-derived; the
+ // usual tk:digest:* shape mints via the curated template factory (stays registered, so
+ // Metadata.reconstruct/Property.get resolve it), anything else keeps the public factory.
+ private static Property mintProperty(String metadataKey) {
+ if (metadataKey.startsWith(DIGEST_KEY_PREFIX)) {
+ return TikaCoreProperties.digestProperty(metadataKey.substring(DIGEST_KEY_PREFIX.length()));
+ }
+ return Property.internalText(metadataKey);
+ }
+
private static void setContentLength(long length, Metadata metadata) {
if (StringUtils.isBlank(metadata.get(Metadata.CONTENT_LENGTH))) {
//only add it if it hasn't been populated already
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Property.java b/tika-core/src/main/java/org/apache/tika/metadata/Property.java
index aa3df83307..da138f3a96 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Property.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Property.java
@@ -136,6 +136,34 @@ public final class Property implements Comparable<Property> {
return new Property(name, internal, propertyType, valueType, choices, null, null, false);
}
+ /**
+ * Guards the public factories: a {@code tk:}/{@code X-TIKA:} name can never be minted
+ * through them. Curated reserved constants go through the package-private
+ * {@code reservedInternal*}/{@code reservedExternal*} family below instead, so any live
+ * {@code Property} carrying a reserved name is, by construction, curated.
+ */
+ private static void requireNotReserved(String name) {
+ if (ReservedNamespaces.isTikaNative(name)) {
+ throw new IllegalArgumentException("'" + name + "' is in the reserved Tika-native "
+ + "namespace (tk:/X-TIKA:); it cannot be minted via a public Property "
+ + "factory. Curated tk: constants belong in org.apache.tika.metadata and "
+ + "mint via the package-private reserved* factories.");
+ }
+ }
+
+ /**
+ * Mirror image of {@link #requireNotReserved(String)} for the reserved factories below:
+ * fail loud (rather than silently minting a non-reserved Property through the reserved
+ * path) if fed a name that isn't actually reserved.
+ */
+ private static void requireReserved(String name) {
+ if (!ReservedNamespaces.isTikaNative(name)) {
+ throw new IllegalArgumentException("'" + name + "' is not in the reserved "
+ + "Tika-native namespace (tk:/X-TIKA:); use a public Property factory "
+ + "instead of the package-private reserved* path.");
+ }
+ }
+
/**
* Get the type of a property
*
@@ -175,90 +203,229 @@ public final class Property implements Comparable<Property> {
}
public static Property internalBoolean(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.BOOLEAN);
}
public static Property internalClosedChoise(String name, String... choices) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.CLOSED_CHOICE, choices);
}
public static Property internalDate(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.DATE);
}
public static Property internalDateBag(String name) {
+ requireNotReserved(name);
return new Property(name, true, PropertyType.BAG, ValueType.DATE);
}
public static Property internalInteger(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.INTEGER);
}
public static Property internalIntegerSequence(String name) {
+ requireNotReserved(name);
return new Property(name, true, PropertyType.SEQ, ValueType.INTEGER);
}
public static Property internalRational(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.RATIONAL);
}
public static Property internalOpenChoise(String name, String... choices) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.OPEN_CHOICE, choices);
}
public static Property internalReal(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.REAL);
}
public static Property internalText(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.TEXT);
}
public static Property internalTextBag(String name) {
+ requireNotReserved(name);
return new Property(name, true, PropertyType.BAG, ValueType.TEXT);
}
public static Property internalURI(String name) {
+ requireNotReserved(name);
return new Property(name, true, ValueType.URI);
}
public static Property externalClosedChoise(String name, String... choices) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.CLOSED_CHOICE, choices);
}
public static Property externalOpenChoise(String name, String... choices) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.OPEN_CHOICE, choices);
}
public static Property externalDate(String name) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.DATE);
}
public static Property externalReal(String name) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.REAL);
}
public static Property externalRealSeq(String name) {
+ requireNotReserved(name);
return new Property(name, false, PropertyType.SEQ, ValueType.REAL);
}
public static Property externalInteger(String name) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.INTEGER);
}
public static Property externalBoolean(String name) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.BOOLEAN);
}
public static Property externalBooleanSeq(String name) {
+ requireNotReserved(name);
return new Property(name, false, PropertyType.SEQ, ValueType.BOOLEAN);
}
public static Property externalText(String name) {
+ requireNotReserved(name);
return new Property(name, false, ValueType.TEXT);
}
public static Property externalTextBag(String name) {
+ requireNotReserved(name);
+ return new Property(name, false, PropertyType.BAG, ValueType.TEXT);
+ }
+
+ // ---- Package-private mirrors for curated tk:/X-TIKA: constants -------------------
+ // Same shapes as the public factories above, but (a) assert the name IS reserved
+ // (b) still register (curated constants must stay resolvable via Property.get /
+ // Metadata.reconstruct). Callers: TikaCoreProperties, TikaPagedText, Rendering — all
+ // in-package. Not for document-derived names; those mint unregistered (see
+ // mintUnregistered) via KeyPrefix (stage 3+).
+
+ static Property reservedInternalBoolean(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.BOOLEAN);
+ }
+
+ static Property reservedInternalClosedChoise(String name, String... choices) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.CLOSED_CHOICE, choices);
+ }
+
+ static Property reservedInternalDate(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.DATE);
+ }
+
+ static Property reservedInternalDateBag(String name) {
+ requireReserved(name);
+ return new Property(name, true, PropertyType.BAG, ValueType.DATE);
+ }
+
+ static Property reservedInternalInteger(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.INTEGER);
+ }
+
+ static Property reservedInternalIntegerSequence(String name) {
+ requireReserved(name);
+ return new Property(name, true, PropertyType.SEQ, ValueType.INTEGER);
+ }
+
+ static Property reservedInternalRational(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.RATIONAL);
+ }
+
+ static Property reservedInternalOpenChoise(String name, String... choices) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.OPEN_CHOICE, choices);
+ }
+
+ static Property reservedInternalReal(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.REAL);
+ }
+
+ static Property reservedInternalText(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.TEXT);
+ }
+
+ static Property reservedInternalTextBag(String name) {
+ requireReserved(name);
+ return new Property(name, true, PropertyType.BAG, ValueType.TEXT);
+ }
+
+ static Property reservedInternalURI(String name) {
+ requireReserved(name);
+ return new Property(name, true, ValueType.URI);
+ }
+
+ static Property reservedExternalClosedChoise(String name, String... choices) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.CLOSED_CHOICE, choices);
+ }
+
+ static Property reservedExternalOpenChoise(String name, String... choices) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.OPEN_CHOICE, choices);
+ }
+
+ static Property reservedExternalDate(String name) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.DATE);
+ }
+
+ static Property reservedExternalReal(String name) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.REAL);
+ }
+
+ static Property reservedExternalRealSeq(String name) {
+ requireReserved(name);
+ return new Property(name, false, PropertyType.SEQ, ValueType.REAL);
+ }
+
+ static Property reservedExternalInteger(String name) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.INTEGER);
+ }
+
+ static Property reservedExternalBoolean(String name) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.BOOLEAN);
+ }
+
+ static Property reservedExternalBooleanSeq(String name) {
+ requireReserved(name);
+ return new Property(name, false, PropertyType.SEQ, ValueType.BOOLEAN);
+ }
+
+ static Property reservedExternalText(String name) {
+ requireReserved(name);
+ return new Property(name, false, ValueType.TEXT);
+ }
+
+ static Property reservedExternalTextBag(String name) {
+ requireReserved(name);
return new Property(name, false, PropertyType.BAG, ValueType.TEXT);
}
@@ -267,6 +434,10 @@ public final class Property implements Comparable<Property> {
* <p>
* Note that name of the composite property is taken from its primary property,
* and primary and secondary properties must not be composite properties themselves.
+ * <p>
+ * No reserved-name check here: {@code primaryProperty} was already validated (or
+ * asserted reserved) at its own mint, and composites never register (see the
+ * constructor), so there is nothing new to forge or intern.
*
* @param primaryProperty
* @param secondaryExtractProperties
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Rendering.java b/tika-core/src/main/java/org/apache/tika/metadata/Rendering.java
index 037dbb6a45..4eab31a2ac 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Rendering.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Rendering.java
@@ -19,6 +19,20 @@ package org.apache.tika.metadata;
public interface Rendering {
String RENDERING_PREFIX = "tk:rendering:";
- Property RENDERED_BY = Property.externalTextBag(RENDERING_PREFIX + "rendered-by");
- Property RENDERED_MS = Property.externalReal(RENDERING_PREFIX + "rendering-time-ms");
+ Property RENDERED_BY = Property.reservedExternalTextBag(RENDERING_PREFIX + "rendered-by");
+ Property RENDERED_MS = Property.reservedExternalReal(RENDERING_PREFIX + "rendering-time-ms");
+
+ /**
+ * Time PDFBox took to render the page to a {@code BufferedImage}.
+ * @see org.apache.tika.renderer.pdf.pdfbox.PDFBoxRenderer
+ */
+ Property PDFBOX_RENDERING_TIME_MS =
+ Property.reservedExternalReal(RENDERING_PREFIX + "pdfbox-rendering-ms");
+
+ /**
+ * Time PDFBox/java took to write the rendered image out (encoding cost varies by format).
+ * @see org.apache.tika.renderer.pdf.pdfbox.PDFBoxRenderer
+ */
+ Property PDFBOX_IMAGE_WRITING_TIME_MS =
+ Property.reservedExternalReal(RENDERING_PREFIX + "pdfbox-image-writing-ms");
}
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/TikaCoreProperties.java b/tika-core/src/main/java/org/apache/tika/metadata/TikaCoreProperties.java
index a977d8c377..0b63adc982 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/TikaCoreProperties.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/TikaCoreProperties.java
@@ -16,6 +16,8 @@
*/
package org.apache.tika.metadata;
+import org.apache.tika.utils.StringUtils;
+
/**
* Contains a core set of basic Tika metadata properties, which all parsers
* will attempt to supply (where the file format permits). These are all
@@ -58,7 +60,31 @@ public interface TikaCoreProperties {
* migrate to {@link #TIKA_META_PREFIX} ({@code tk:}) — see the 3.x-to-4.x metadata key table.
*/
String LEGACY_TIKA_META_PREFIX = "X-TIKA" + NAMESPACE_PREFIX_DELIMITER;
- Property EMBEDDED_DEPTH = Property.internalInteger(TIKA_META_PREFIX + "embedded-depth");
+
+ /**
+ * Template factory for digest keys: {@code tk:digest:<suffix>}, e.g.
+ * {@code tk:digest:SHA256} or {@code tk:digest:SHA256:BASE32} (algorithm[:encoding],
+ * config-supplied by a {@code Digester}, never document-derived). Validates only that
+ * {@code suffix} is non-empty and contains no whitespace — the shape constraint is
+ * hygiene, not security, since the input is trusted config.
+ *
+ * @since Apache Tika 4.0.0
+ */
+ static Property digestProperty(String suffix) {
+ if (StringUtils.isBlank(suffix)) {
+ throw new IllegalArgumentException("digest suffix must not be blank");
+ }
+ for (int i = 0; i < suffix.length(); i++) {
+ if (Character.isWhitespace(suffix.charAt(i))) {
+ throw new IllegalArgumentException(
+ "digest suffix must not contain whitespace: '" + suffix + "'");
+ }
+ }
+ return Property.reservedInternalText(
+ TIKA_META_PREFIX + "digest" + NAMESPACE_PREFIX_DELIMITER + suffix);
+ }
+
+ Property EMBEDDED_DEPTH = Property.reservedInternalInteger(TIKA_META_PREFIX + "embedded-depth");
/**
* This tracks the embedded file paths based on the name of embedded files
@@ -71,7 +97,7 @@ public interface TikaCoreProperties {
* For a more robust path, see {@link TikaCoreProperties#EMBEDDED_ID_PATH}.
*/
Property EMBEDDED_RESOURCE_PATH =
- Property.internalText(TIKA_META_PREFIX + "embedded-resource-path");
+ Property.reservedInternalText(TIKA_META_PREFIX + "embedded-resource-path");
/**
@@ -94,28 +120,28 @@ public interface TikaCoreProperties {
* For a more robust path, see {@link TikaCoreProperties#EMBEDDED_ID_PATH}.
*/
Property FINAL_EMBEDDED_RESOURCE_PATH =
- Property.internalText(TIKA_META_PREFIX + "final-embedded-resource-path");
+ Property.reservedInternalText(TIKA_META_PREFIX + "final-embedded-resource-path");
/**
* This tracks the embedded file paths based on the embedded file's
* {@link TikaCoreProperties#EMBEDDED_ID}.
*/
Property EMBEDDED_ID_PATH =
- Property.internalText(TIKA_META_PREFIX + "embedded-id-path");
+ Property.reservedInternalText(TIKA_META_PREFIX + "embedded-id-path");
/**
* This is a 1-index counter for embedded files, used by the RecursiveParserWrapper
*/
Property EMBEDDED_ID =
- Property.internalInteger(TIKA_META_PREFIX + "embedded-id");
+ Property.reservedInternalInteger(TIKA_META_PREFIX + "embedded-id");
- Property PARSE_TIME_MILLIS = Property.internalText(TIKA_META_PREFIX + "parse-time-millis");
+ Property PARSE_TIME_MILLIS = Property.reservedInternalText(TIKA_META_PREFIX + "parse-time-millis");
/**
* Simple class name of the content handler.
* @deprecated Use {@link #TIKA_CONTENT_HANDLER_TYPE} for the handler type enum value.
*/
@Deprecated
- Property TIKA_CONTENT_HANDLER = Property.internalText(TIKA_META_PREFIX + "content-handler");
+ Property TIKA_CONTENT_HANDLER = Property.reservedInternalText(TIKA_META_PREFIX + "content-handler");
/**
* The handler type used to produce {@link #TIKA_CONTENT}.
@@ -123,15 +149,15 @@ public interface TikaCoreProperties {
* enum name (e.g. {@code TEXT}, {@code MARKDOWN}, {@code HTML}, {@code XML}).
*/
Property TIKA_CONTENT_HANDLER_TYPE =
- Property.internalText(TIKA_META_PREFIX + "content-handler-type");
- Property TIKA_CONTENT = Property.internalText(TIKA_META_PREFIX + "content");
+ Property.reservedInternalText(TIKA_META_PREFIX + "content-handler-type");
+ Property TIKA_CONTENT = Property.reservedInternalText(TIKA_META_PREFIX + "content");
/**
* JSON array of chunks (text segments with optional embedding vectors and locators).
* Used by inference parsers and metadata filters to attach chunked representations
* of document content for downstream indexing and semantic search.
*/
- Property TIKA_CHUNKS = Property.internalText("tk:chunks");
+ Property TIKA_CHUNKS = Property.reservedInternalText("tk:chunks");
/**
* Use this to store parse exception information in the Metadata object.
*/
@@ -144,28 +170,36 @@ public interface TikaCoreProperties {
//exception in main file
Property CONTAINER_EXCEPTION =
- Property.internalText(TIKA_META_EXCEPTION_PREFIX + "container-exception");
+ Property.reservedInternalText(TIKA_META_EXCEPTION_PREFIX + "container-exception");
//exception in an embedded file
Property EMBEDDED_EXCEPTION =
- Property.internalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-exception");
+ Property.reservedInternalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-exception");
//exception handling the raw bytes of an embedded file by an EmbeddedDocumentByteStore
Property EMBEDDED_BYTES_EXCEPTION =
- Property.internalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-bytes-exception");
+ Property.reservedInternalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-bytes-exception");
//warning while parsing in an embedded file
Property EMBEDDED_WARNING =
- Property.internalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-warning");
+ Property.reservedInternalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-warning");
+
+ /**
+ * Identifies the embedded {@link org.apache.tika.parser.Parser} that produced
+ * {@link #EMBEDDED_EXCEPTION}, when known.
+ * @see org.apache.tika.utils.ParserUtils#EMBEDDED_PARSER the alias parser code uses
+ */
+ Property EMBEDDED_PARSER =
+ Property.reservedInternalText(TIKA_META_EXCEPTION_PREFIX + "embedded-parser");
Property WRITE_LIMIT_REACHED =
- Property.internalBoolean(TIKA_META_EXCEPTION_PREFIX + "write-limit-reached");
+ Property.reservedInternalBoolean(TIKA_META_EXCEPTION_PREFIX + "write-limit-reached");
Property EMBEDDED_RESOURCE_LIMIT_REACHED =
- Property.internalBoolean(TIKA_META_EXCEPTION_PREFIX + "embedded-resource-limit-reached");
+ Property.reservedInternalBoolean(TIKA_META_EXCEPTION_PREFIX + "embedded-resource-limit-reached");
Property EMBEDDED_DEPTH_LIMIT_REACHED =
- Property.internalBoolean(TIKA_META_EXCEPTION_PREFIX + "embedded-depth-limit-reached");
+ Property.reservedInternalBoolean(TIKA_META_EXCEPTION_PREFIX + "embedded-depth-limit-reached");
/**
* Use this to store exceptions caught during a parse that are
@@ -174,7 +208,7 @@ public interface TikaCoreProperties {
* a dependency.
*/
Property TIKA_META_EXCEPTION_WARNING =
- Property.internalTextBag(TIKA_META_EXCEPTION_PREFIX + "warn");
+ Property.reservedInternalTextBag(TIKA_META_EXCEPTION_PREFIX + "warn");
/**
* This means that metadata keys or metadata values were truncated.
@@ -182,7 +216,7 @@ public interface TikaCoreProperties {
* a field is not in the "include" set.
*/
Property TRUNCATED_METADATA =
- Property.internalBoolean(TIKA_META_WARN_PREFIX + "truncated-metadata");
+ Property.reservedInternalBoolean(TIKA_META_WARN_PREFIX + "truncated-metadata");
/**
* This indicates that only a portion of the file content was provided for detection.
@@ -190,7 +224,7 @@ public interface TikaCoreProperties {
* (e.g., not returning a detection result that requires reading to end of file).
*/
Property TRUNCATED_CONTENT_FOR_DETECTION =
- Property.internalBoolean(TIKA_META_PREFIX + "truncated-content-for-detection");
+ Property.reservedInternalBoolean(TIKA_META_PREFIX + "truncated-content-for-detection");
/**
* When content is truncated for detection, this stores the number of bytes
@@ -198,7 +232,7 @@ public interface TikaCoreProperties {
* to set appropriate mark limits.
*/
Property DETECTION_CONTENT_LENGTH =
- Property.internalInteger(TIKA_META_PREFIX + "detection-content-length");
+ Property.reservedInternalInteger(TIKA_META_PREFIX + "detection-content-length");
/**
* Use this to store exceptions caught while trying to read the
@@ -206,34 +240,34 @@ public interface TikaCoreProperties {
* a parse exception on the embedded resource.
*/
Property TIKA_META_EXCEPTION_EMBEDDED_STREAM =
- Property.internalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-stream-exception");
- Property TIKA_PARSED_BY = Property.internalTextBag(TIKA_META_PREFIX + "parsed-by");
+ Property.reservedInternalTextBag(TIKA_META_EXCEPTION_PREFIX + "embedded-stream-exception");
+ Property TIKA_PARSED_BY = Property.reservedInternalTextBag(TIKA_META_PREFIX + "parsed-by");
/**
* Use this to store a record of all parsers that touched a given file
* in the container file's metadata.
*/
- Property TIKA_PARSED_BY_FULL_SET = Property.internalTextBag(TIKA_META_PREFIX + "parsed-by-full-set");
+ Property TIKA_PARSED_BY_FULL_SET = Property.reservedInternalTextBag(TIKA_META_PREFIX + "parsed-by-full-set");
- Property TIKA_DETECTED_LANGUAGE = Property.externalTextBag(TIKA_META_PREFIX +
+ Property TIKA_DETECTED_LANGUAGE = Property.reservedExternalTextBag(TIKA_META_PREFIX +
"detected-language");
- Property TIKA_DETECTED_LANGUAGE_CONFIDENCE = Property.externalTextBag(TIKA_META_PREFIX +
+ Property TIKA_DETECTED_LANGUAGE_CONFIDENCE = Property.reservedExternalTextBag(TIKA_META_PREFIX +
"detected-language-confidence");
- Property TIKA_DETECTED_LANGUAGE_CONFIDENCE_RAW = Property.externalRealSeq(TIKA_META_PREFIX +
+ Property TIKA_DETECTED_LANGUAGE_CONFIDENCE_RAW = Property.reservedExternalRealSeq(TIKA_META_PREFIX +
"detected-language-confidence-raw");
- Property RESOURCE_NAME_KEY = Property.internalText(TIKA_META_PREFIX + "resource-name");
+ Property RESOURCE_NAME_KEY = Property.reservedInternalText(TIKA_META_PREFIX + "resource-name");
/**
* Indicates that the file extension on the resource name was inferred by Tika
* (e.g., from content type detection) rather than provided by the original document.
*/
Property RESOURCE_NAME_EXTENSION_INFERRED =
- Property.externalBoolean(TIKA_META_PREFIX + "resource-name-extension-inferred");
+ Property.reservedExternalBoolean(TIKA_META_PREFIX + "resource-name-extension-inferred");
- Property EMBEDDED_RELATIONSHIP_ID = Property.internalText(TIKA_META_PREFIX + "embedded-relationship-id");
+ Property EMBEDDED_RELATIONSHIP_ID = Property.reservedInternalText(TIKA_META_PREFIX + "embedded-relationship-id");
String EMBEDDED_RESOURCE_TYPE_KEY = "tk:embedded-resource-type";
/**
@@ -242,7 +276,7 @@ public interface TikaCoreProperties {
* within the file.
*/
Property ORIGINAL_RESOURCE_NAME =
- Property.internalTextBag(TIKA_META_PREFIX + "orig-resource-name");
+ Property.reservedInternalTextBag(TIKA_META_PREFIX + "orig-resource-name");
/**
* This should be used to store the path (relative or full)
* of the source/container file, including the file name,
@@ -250,13 +284,13 @@ public interface TikaCoreProperties {
* <p>
* This can also be used for a primary key within a database.
*/
- Property SOURCE_PATH = Property.internalText(TIKA_META_PREFIX + "source-path");
+ Property SOURCE_PATH = Property.reservedInternalText(TIKA_META_PREFIX + "source-path");
/**
* This records the metadata as stored within a file for an embedded file's path
* including the file name. For example a zip file may include an msg with this path: /my-emails/important/this.msg
*/
- Property INTERNAL_PATH = Property.internalText(TIKA_META_PREFIX + "internal-path");
+ Property INTERNAL_PATH = Property.reservedInternalText(TIKA_META_PREFIX + "internal-path");
/**
* This is currently used to identify Content-Type that may be
@@ -265,25 +299,25 @@ public interface TikaCoreProperties {
* , or the value might come from outside the document. This information
* may be faulty and should be treated only as a hint.
*/
- Property CONTENT_TYPE_HINT = Property.internalText(TIKA_META_PREFIX + "content-type-hint");
+ Property CONTENT_TYPE_HINT = Property.reservedInternalText(TIKA_META_PREFIX + "content-type-hint");
/**
* This is used by users to override detection with the override detector.
*/
Property CONTENT_TYPE_USER_OVERRIDE =
- Property.internalText(TIKA_META_PREFIX + "content-type-override");
+ Property.reservedInternalText(TIKA_META_PREFIX + "content-type-override");
/**
* This is used by parsers to override detection of embedded resources
* with the override detector.
*/
Property CONTENT_TYPE_PARSER_OVERRIDE =
- Property.internalText(TIKA_META_PREFIX + "content-type-parser-override");
+ Property.reservedInternalText(TIKA_META_PREFIX + "content-type-parser-override");
/**
* This is set by DefaultDetector to store the result of MimeTypes (magic byte)
* detection. This allows downstream detectors to use it as a hint without
* re-running magic detection.
*/
Property CONTENT_TYPE_MAGIC_DETECTED =
- Property.internalText(TIKA_META_PREFIX + "content-type-magic-detected");
+ Property.reservedInternalText(TIKA_META_PREFIX + "content-type-magic-detected");
/**
* @see DublinCore#FORMAT
*/
@@ -396,7 +430,7 @@ public interface TikaCoreProperties {
* Java's {@link javax.imageio.ImageReader#getNumImages(boolean)}. See
* the javadocs for known limitations.
*/
- Property NUM_IMAGES = Property.internalInteger("tk:num-images");
+ Property NUM_IMAGES = Property.reservedInternalInteger("tk:num-images");
// Comment and rating properties
/**
@@ -406,21 +440,21 @@ public interface TikaCoreProperties {
/**
* Embedded resource type property
*/
- Property EMBEDDED_RESOURCE_TYPE = Property.internalClosedChoise(EMBEDDED_RESOURCE_TYPE_KEY,
+ Property EMBEDDED_RESOURCE_TYPE = Property.reservedInternalClosedChoise(EMBEDDED_RESOURCE_TYPE_KEY,
EmbeddedResourceType.ATTACHMENT.toString(), EmbeddedResourceType.INLINE.toString(),
EmbeddedResourceType.METADATA.toString(), EmbeddedResourceType.MACRO.toString(),
EmbeddedResourceType.THUMBNAIL.toString(), EmbeddedResourceType.RENDERING.toString());
- Property HAS_SIGNATURE = Property.internalBoolean("tk:has-signature");
+ Property HAS_SIGNATURE = Property.reservedInternalBoolean("tk:has-signature");
- Property SIGNATURE_NAME = Property.internalTextBag("tk:signature:name");
- Property SIGNATURE_DATE = Property.internalDateBag("tk:signature:date");
- Property SIGNATURE_LOCATION = Property.internalTextBag("tk:signature:location");
- Property SIGNATURE_REASON = Property.internalTextBag("tk:signature:reason");
- Property SIGNATURE_FILTER = Property.internalTextBag("tk:signature:filter");
- Property SIGNATURE_CONTACT_INFO = Property.internalTextBag("tk:signature:contact-info");
+ Property SIGNATURE_NAME = Property.reservedInternalTextBag("tk:signature:name");
+ Property SIGNATURE_DATE = Property.reservedInternalDateBag("tk:signature:date");
+ Property SIGNATURE_LOCATION = Property.reservedInternalTextBag("tk:signature:location");
+ Property SIGNATURE_REASON = Property.reservedInternalTextBag("tk:signature:reason");
+ Property SIGNATURE_FILTER = Property.reservedInternalTextBag("tk:signature:filter");
+ Property SIGNATURE_CONTACT_INFO = Property.reservedInternalTextBag("tk:signature:contact-info");
//is the file encrypted
- Property IS_ENCRYPTED = Property.internalBoolean(TIKA_META_PREFIX + "encrypted");
+ Property IS_ENCRYPTED = Property.reservedInternalBoolean(TIKA_META_PREFIX + "encrypted");
/**
* When an EncodingDetector detects an encoding, the encoding should be stored in this field.
@@ -429,14 +463,14 @@ public interface TikaCoreProperties {
* may choose to use a default encoding. We want to differentiate between a parser using a
* default encoding and the output of an EncodingDetector.
*/
- Property DETECTED_ENCODING = Property.externalText(TIKA_META_PREFIX + "detected-encoding");
+ Property DETECTED_ENCODING = Property.reservedExternalText(TIKA_META_PREFIX + "detected-encoding");
/**
* This should be the simple class name for the EncodingDetectors whose detected encoding
* was used in the parse.
*/
- Property ENCODING_DETECTOR = Property.externalText(TIKA_META_PREFIX + "encoding-detector");
+ Property ENCODING_DETECTOR = Property.reservedExternalText(TIKA_META_PREFIX + "encoding-detector");
/**
* Diagnostic trace showing which encoding detectors ran and what each returned,
@@ -444,7 +478,7 @@ public interface TikaCoreProperties {
* Example: {@code "HtmlEncodingDetector->UTF-8, Icu4jEncodingDetector->windows-1256 (scored)"}
*/
Property ENCODING_DETECTION_TRACE =
- Property.externalText(TIKA_META_PREFIX + "encoding-detection-trace");
+ Property.reservedExternalText(TIKA_META_PREFIX + "encoding-detection-trace");
/**
* The charset actually used to decode the stream when a superset override was applied.
@@ -456,13 +490,13 @@ public interface TikaCoreProperties {
* the same charset.
*/
Property DECODED_CHARSET =
- Property.externalText(TIKA_META_PREFIX + "decoded-charset");
+ Property.reservedExternalText(TIKA_META_PREFIX + "decoded-charset");
/**
* General metadata key for the count of non-final versions available within a file. This
* was added initially to support generalizing incremental updates in PDF.
*/
- Property VERSION_COUNT = Property.externalInteger(TIKA_META_PREFIX + "version-count");
+ Property VERSION_COUNT = Property.reservedExternalInteger(TIKA_META_PREFIX + "version-count");
/**
* General metadata key for the version number of a given file that contains
@@ -470,9 +504,9 @@ public interface TikaCoreProperties {
* The latest version does not have this metadata value. This was added initially
* to support generalizing incremental updates in PDF.
*/
- Property VERSION_NUMBER = Property.externalInteger(TIKA_META_PREFIX + "version-number");
+ Property VERSION_NUMBER = Property.reservedExternalInteger(TIKA_META_PREFIX + "version-number");
- Property PIPES_RESULT = Property.externalText(TIKA_META_PREFIX + "pipes-result");
+ Property PIPES_RESULT = Property.reservedExternalText(TIKA_META_PREFIX + "pipes-result");
/**
* A file might contain different types of embedded documents.
* The most common is the ATTACHMENT.
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/TikaPagedText.java b/tika-core/src/main/java/org/apache/tika/metadata/TikaPagedText.java
index 76faee52bd..0251e01fd0 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/TikaPagedText.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/TikaPagedText.java
@@ -30,7 +30,7 @@ public interface TikaPagedText {
* anchored to exactly one page; for resources spanning multiple pages
* see {@link #PAGE_NUMBERS}.
*/
- Property PAGE_NUMBER = Property.internalInteger(TIKA_PAGED_TEXT_PREFIX + "number");
+ Property PAGE_NUMBER = Property.reservedInternalInteger(TIKA_PAGED_TEXT_PREFIX + "number");
/**
* 1-based page numbers an embedded resource is anchored to, as a
@@ -56,7 +56,7 @@ public interface TikaPagedText {
* </ul>
*/
Property PAGE_NUMBERS =
- Property.internalIntegerSequence(TIKA_PAGED_TEXT_PREFIX + "numbers");
+ Property.reservedInternalIntegerSequence(TIKA_PAGED_TEXT_PREFIX + "numbers");
/**
* Sentinel value used as the sole element of {@link #PAGE_NUMBERS}
@@ -66,5 +66,5 @@ public interface TikaPagedText {
*/
int UNLINKED_PAGE = -1;
- Property PAGE_ROTATION = Property.internalRational(TIKA_PAGED_TEXT_PREFIX + "rotation");
+ Property PAGE_ROTATION = Property.reservedInternalRational(TIKA_PAGED_TEXT_PREFIX + "rotation");
}
diff --git a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
index 3536cf83e6..e194536076 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
@@ -31,8 +31,11 @@ import org.apache.tika.parser.ParserDecorator;
*/
public class ParserUtils {
- public final static Property EMBEDDED_PARSER = Property.internalText(
- TikaCoreProperties.TIKA_META_EXCEPTION_PREFIX + "embedded-parser");
+ /**
+ * @see TikaCoreProperties#EMBEDDED_PARSER the curated Property this aliases; {@code tk:}
+ * Properties mint only in org.apache.tika.metadata (see Property mint-time validation)
+ */
+ public final static Property EMBEDDED_PARSER = TikaCoreProperties.EMBEDDED_PARSER;
/**
diff --git a/tika-core/src/test/java/org/apache/tika/metadata/PropertyReservedNameTest.java b/tika-core/src/test/java/org/apache/tika/metadata/PropertyReservedNameTest.java
new file mode 100644
index 0000000000..dc1708f23b
--- /dev/null
+++ b/tika-core/src/test/java/org/apache/tika/metadata/PropertyReservedNameTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.metadata;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Stage 2 mint-time validation: the public {@code Property} factories reject reserved
+ * ({@code tk:}/{@code X-TIKA:}) names, curated reserved constants mint through the
+ * package-private path instead, and the digest template factory produces usable Properties.
+ */
+public class PropertyReservedNameTest {
+
+ // Every public static factory that mints a (non-composite) Property, keyed by method
+ // name for readable failure messages. Table-driven so one test method covers all 22
+ // shapes instead of 22 near-identical methods (no junit-jupiter-params in this module).
+ private static final Map<String, Function<String, Property>> PUBLIC_FACTORIES = publicFactories();
+
+ private static Map<String, Function<String, Property>> publicFactories() {
+ Map<String, Function<String, Property>> m = new LinkedHashMap<>();
+ m.put("internalBoolean", Property::internalBoolean);
+ m.put("internalClosedChoise", n -> Property.internalClosedChoise(n, "a", "b"));
+ m.put("internalDate", Property::internalDate);
+ m.put("internalDateBag", Property::internalDateBag);
+ m.put("internalInteger", Property::internalInteger);
+ m.put("internalIntegerSequence", Property::internalIntegerSequence);
+ m.put("internalRational", Property::internalRational);
+ m.put("internalOpenChoise", n -> Property.internalOpenChoise(n, "a", "b"));
+ m.put("internalReal", Property::internalReal);
+ m.put("internalText", Property::internalText);
+ m.put("internalTextBag", Property::internalTextBag);
+ m.put("internalURI", Property::internalURI);
+ m.put("externalClosedChoise", n -> Property.externalClosedChoise(n, "a", "b"));
+ m.put("externalOpenChoise", n -> Property.externalOpenChoise(n, "a", "b"));
+ m.put("externalDate", Property::externalDate);
+ m.put("externalReal", Property::externalReal);
+ m.put("externalRealSeq", Property::externalRealSeq);
+ m.put("externalInteger", Property::externalInteger);
+ m.put("externalBoolean", Property::externalBoolean);
+ m.put("externalBooleanSeq", Property::externalBooleanSeq);
+ m.put("externalText", Property::externalText);
+ m.put("externalTextBag", Property::externalTextBag);
+ return m;
+ }
+
+ @Test
+ public void testAllPublicFactoriesRejectTkPrefix() {
+ for (Map.Entry<String, Function<String, Property>> e : PUBLIC_FACTORIES.entrySet()) {
+ String label = e.getKey();
+ Function<String, Property> factory = e.getValue();
+ String name = "tk:prop-test-reserved-" + label + "-" + System.nanoTime();
+ assertThrows(IllegalArgumentException.class, () -> factory.apply(name),
+ label + " must reject a tk: name");
+ }
+ }
+
+ @Test
+ public void testAllPublicFactoriesRejectLegacyXTikaPrefix() {
+ for (Map.Entry<String, Function<String, Property>> e : PUBLIC_FACTORIES.entrySet()) {
+ String label = e.getKey();
+ Function<String, Property> factory = e.getValue();
+ String name = "X-TIKA:prop-test-reserved-" + label + "-" + System.nanoTime();
+ assertThrows(IllegalArgumentException.class, () -> factory.apply(name),
+ label + " must reject an X-TIKA: name");
+ }
+ }
+
+ @Test
+ public void testAllPublicFactoriesStillWorkForNonReservedNames() {
+ for (Map.Entry<String, Function<String, Property>> e : PUBLIC_FACTORIES.entrySet()) {
+ String label = e.getKey();
+ String name = "prop-test:reserved-ok-" + label + "-" + System.nanoTime();
+ Property p = e.getValue().apply(name);
+ assertEquals(name, p.getName(), label + " should still mint a non-reserved name");
+ assertSame(p, Property.get(name), label + " should still register");
+ }
+ }
+
+ @Test
+ public void testReservedFactoryRegistersAndResolvesViaPropertyGet() {
+ String name = "tk:prop-test-curated-" + System.nanoTime();
+ Property p = Property.reservedInternalText(name);
+ assertSame(p, Property.get(name), "curated tk: constants must stay in the global registry");
+ }
+
+ @Test
+ public void testReservedFactoryRejectsNonReservedName() {
+ // symmetry check: the reserved path must not silently accept a non-reserved name
+ String name = "prop-test:not-reserved-" + System.nanoTime();
+ assertThrows(IllegalArgumentException.class, () -> Property.reservedInternalText(name));
+ }
+
+ @Test
+ public void testDigestPropertyProducesUsableRegisteredProperty() {
+ String suffix = "SHA256-" + System.nanoTime();
+ Property p = TikaCoreProperties.digestProperty(suffix);
+
+ assertEquals("tk:digest:" + suffix, p.getName());
+ assertSame(p, Property.get(p.getName()), "digest Properties must register");
+
+ Metadata metadata = new Metadata();
+ metadata.set(p, "abc123");
+ assertEquals("abc123", metadata.get(p.getName()));
+ }
+
+ @Test
+ public void testDigestPropertyRejectsBlankOrNullSuffix() {
+ assertThrows(IllegalArgumentException.class, () -> TikaCoreProperties.digestProperty(""));
+ assertThrows(IllegalArgumentException.class, () -> TikaCoreProperties.digestProperty(null));
+ assertThrows(IllegalArgumentException.class, () -> TikaCoreProperties.digestProperty(" "));
+ }
+
+ @Test
+ public void testDigestPropertyRejectsWhitespaceInSuffix() {
+ assertThrows(IllegalArgumentException.class,
+ () -> TikaCoreProperties.digestProperty("SHA 256"));
+ }
+
+ @Test
+ public void testDigestPropertyAllowsColonSeparatedEncodingSuffix() {
+ // real shape produced by DigestDef.metadataKey(), e.g. "SHA256:BASE32"
+ String suffix = "SHA256:BASE32-" + System.nanoTime();
+ Property p = TikaCoreProperties.digestProperty(suffix);
+ assertEquals("tk:digest:" + suffix, p.getName());
+ }
+
+ @Test
+ public void testMintUnregisteredPathUnaffectedByReservedNameChecks() {
+ // mintUnregistered carries no reserved-name validation either way -- it is the
+ // non-registering path used by curated in-package code today and by KeyPrefix from
+ // stage 3; this documents that stage 2 left it untouched.
+ String name = "tk:prop-test-unregistered-" + System.nanoTime();
+ Property p = Property.mintUnregistered(name, true, Property.PropertyType.SIMPLE,
+ Property.ValueType.TEXT, null);
+ assertEquals(name, p.getName());
+ assertNull(Property.get(name), "mintUnregistered must never intern");
+ }
+}
diff --git a/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ProfilerBase.java b/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ProfilerBase.java
index 330455e610..8a26269383 100644
--- a/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ProfilerBase.java
+++ b/tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ProfilerBase.java
@@ -71,7 +71,6 @@ import org.apache.tika.language.detect.LanguageResult;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.PDF;
import org.apache.tika.metadata.PagedText;
-import org.apache.tika.metadata.Property;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.ml.junkdetect.JunkDetector;
import org.apache.tika.pipes.api.fetcher.FetchKey;
@@ -86,8 +85,10 @@ public abstract class ProfilerBase {
protected static final AtomicInteger ID = new AtomicInteger();
static final long NON_EXISTENT_FILE_LENGTH = -1l;
final static int FILE_PATH_MAX_LEN = 1024;//max len for varchar for file_path
- //Container exception key from the 1.x branch
- private static final Property CONTAINER_EXCEPTION_1X = Property.externalText("X-TIKA" + ":EXCEPTION:runtime");
+ //Container exception key from the 1.x branch. Read-only lookup against legacy extract
+ //JSON, so a plain String key suffices -- Metadata.get(Property) is just get(name) anyway,
+ //and X-TIKA: is reserved: only org.apache.tika.metadata may mint a Property for it.
+ private static final String CONTAINER_EXCEPTION_1X = "X-TIKA" + ":EXCEPTION:runtime";
private static final Logger LOG = LoggerFactory.getLogger(ProfilerBase.class);
private static final String[] EXTRACT_EXTENSIONS = {".json", ".txt", ""};
private static final String[] COMPRESSION_EXTENSIONS = {"", ".bz2", ".gzip", ".zip",};
diff --git a/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-key-fields.json b/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-key-fields.json
index bba36567e8..94488c9af7 100644
--- a/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-key-fields.json
+++ b/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-key-fields.json
@@ -387,6 +387,8 @@
{"class":"org.apache.tika.metadata.RTFMetadata","field":"EMBEDDED_ITEM","key":"rtf:embedded-item"},
{"class":"org.apache.tika.metadata.RTFMetadata","field":"EMBEDDED_TOPIC","key":"rtf:embedded-topic"},
{"class":"org.apache.tika.metadata.RTFMetadata","field":"THUMBNAIL","key":"rtf:thumbnail"},
+ {"class":"org.apache.tika.metadata.Rendering","field":"PDFBOX_IMAGE_WRITING_TIME_MS","key":"tk:rendering:pdfbox-image-writing-ms"},
+ {"class":"org.apache.tika.metadata.Rendering","field":"PDFBOX_RENDERING_TIME_MS","key":"tk:rendering:pdfbox-rendering-ms"},
{"class":"org.apache.tika.metadata.Rendering","field":"RENDERED_BY","key":"tk:rendering:rendered-by"},
{"class":"org.apache.tika.metadata.Rendering","field":"RENDERED_MS","key":"tk:rendering:rendering-time-ms"},
{"class":"org.apache.tika.metadata.TIFF","field":"BITS_PER_SAMPLE","key":"tiff:BitsPerSample"},
@@ -433,6 +435,7 @@
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_EXCEPTION","key":"tk:exception:embedded-exception"},
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_ID","key":"tk:embedded-id"},
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_ID_PATH","key":"tk:embedded-id-path"},
+ {"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_PARSER","key":"tk:exception:embedded-parser"},
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_RELATIONSHIP_ID","key":"tk:embedded-relationship-id"},
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_RESOURCE_LIMIT_REACHED","key":"tk:exception:embedded-resource-limit-reached"},
{"class":"org.apache.tika.metadata.TikaCoreProperties","field":"EMBEDDED_RESOURCE_PATH","key":"tk:embedded-resource-path"},
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java
index fad55d468c..8c5c94592a 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java
@@ -64,17 +64,21 @@ public class PDFBoxRenderer implements PDDocumentRenderer {
/**
* This is the amount of time it takes for PDFBox to render the page
* to a BufferedImage
+ *
+ * @see Rendering#PDFBOX_RENDERING_TIME_MS the curated Property this aliases; {@code tk:}
+ * Properties mint only in org.apache.tika.metadata (see Property mint-time validation)
*/
- public static Property PDFBOX_RENDERING_TIME_MS =
- Property.externalReal(Rendering.RENDERING_PREFIX + "pdfbox-rendering-ms");
+ public static final Property PDFBOX_RENDERING_TIME_MS = Rendering.PDFBOX_RENDERING_TIME_MS;
/**
* This is the amount of time it takes for PDFBox/java to write the image after
* it has been rendered into a BufferedImage. Some formats take much longer
* to encode than others.
+ *
+ * @see Rendering#PDFBOX_IMAGE_WRITING_TIME_MS the curated Property this aliases
*/
- public static Property PDFBOX_IMAGE_WRITING_TIME_MS =
- Property.externalReal(Rendering.RENDERING_PREFIX + "pdfbox-image-writing-ms");
+ public static final Property PDFBOX_IMAGE_WRITING_TIME_MS =
+ Rendering.PDFBOX_IMAGE_WRITING_TIME_MS;
@Override
public Set<MediaType> getSupportedTypes(ParseContext context) {