This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4809-metadata-key-api in repository https://gitbox.apache.org/repos/asf/tika.git
commit c904efa1f4b88cd4167cd0f82fa5536a2b5b2dfe Author: tallison <[email protected]> AuthorDate: Tue Aug 11 12:29:22 2026 -0400 TIKA-4809 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) {
