InferenceUnit.java
/*
* 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.parser.inference;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
/**
* One thing offered to the dispatcher: the bytes of an image, a page render, a media clip,
* with the document they came from and, when known, its parent. Captured at the offer so
* a task can run after the document has closed; the bytes live in a file the dispatcher
* owns until the flush, and the id paths let the flush find the metadata the recursive
* wrapper kept once the parser's own objects are no longer read. A task writes its result
* on the {@link #getDestination() destination}, which the dispatcher decides: the parent
* for an inline part or a page render, the document itself otherwise, and the top-level
* document for everything when the parse produces one metadata object.
*/
public final class InferenceUnit {
private static final Set<String> LIFTED = Set.of(
TikaCoreProperties.EmbeddedResourceType.INLINE.name(),
TikaCoreProperties.EmbeddedResourceType.RENDERING.name());
private final InputKind kind;
private final MediaType type;
private final Metadata target;
private final Metadata parent;
private final Metadata destination;
private final String targetIdPath;
private final String parentIdPath;
private final Path path;
private final String text;
private final long size;
private final int page;
public InferenceUnit(InputKind kind, MediaType type, Metadata target, Metadata parent,
Path path) throws IOException {
this(kind, type, target, parent, path, -1);
}
/** A unit that is one page of its target: {@code page} is 1-based. */
public InferenceUnit(InputKind kind, MediaType type, Metadata target, Metadata parent,
Path path, int page) throws IOException {
this(kind, type, target, parent, target.get(TikaCoreProperties.EMBEDDED_ID_PATH),
parent == null ? null : parent.get(TikaCoreProperties.EMBEDDED_ID_PATH), path,
page);
}
/** As above, with id paths the dispatcher resolved for documents the wrapper did not number. */
InferenceUnit(InputKind kind, MediaType type, Metadata target, Metadata parent,
String targetIdPath, String parentIdPath, Path path, int page)
throws IOException {
this(kind, type, target, parent, placed(target, parent, targetIdPath), targetIdPath,
parentIdPath, path, null, Files.size(path), page);
}
/** A {@link InputKind#TEXT} unit: the target's extracted text, held in memory. */
public InferenceUnit(MediaType type, Metadata target, Metadata parent, String text) {
this(InputKind.TEXT, type, target, parent, target,
target.get(TikaCoreProperties.EMBEDDED_ID_PATH),
parent == null ? null : parent.get(TikaCoreProperties.EMBEDDED_ID_PATH), null,
text, text.getBytes(StandardCharsets.UTF_8).length, -1);
}
private InferenceUnit(InputKind kind, MediaType type, Metadata target, Metadata parent,
Metadata destination, String targetIdPath, String parentIdPath,
Path path, String text, long size, int page) {
this.kind = kind;
this.type = type;
this.target = target;
this.parent = parent;
this.destination = destination;
this.targetIdPath = targetIdPath;
this.parentIdPath = parentIdPath;
this.path = path;
this.text = text;
this.size = size;
this.page = page;
}
/** The same unit aimed at the metadata still read, writing on {@code destination}. */
InferenceUnit aimed(Metadata target, Metadata parent, Metadata destination) {
return new InferenceUnit(kind, type, target, parent, destination, targetIdPath,
parentIdPath, path, text, size, page);
}
/** Whether results for this document belong on the one it is part of, not on it. */
public static boolean lifts(Metadata target) {
String type = target.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE);
return type != null && LIFTED.contains(type);
}
/** The parent for an inline part or a page render that has one, the target otherwise. */
static Metadata placed(Metadata target, Metadata parent, String targetIdPath) {
return parent != null && targetIdPath != null && lifts(target) ? parent : target;
}
/** Where a task writes this unit's results. */
public Metadata getDestination() {
return destination;
}
/** Whether the results go on another document than the target, which a locator then names. */
public boolean isLifted() {
return destination != target;
}
/** The text of a {@link InputKind#TEXT} unit; null for the rest. */
public String getText() {
return text;
}
/** The 1-based page this unit renders, for {@link InputKind#PAGES}; -1 otherwise. */
public int getPage() {
return page;
}
public InputKind getKind() {
return kind;
}
public MediaType getType() {
return type;
}
/** The document the bytes belong to. */
public Metadata getTarget() {
return target;
}
/** The document the target is embedded in; null at top level. */
public Metadata getParent() {
return parent;
}
public String getTargetIdPath() {
return targetIdPath;
}
public String getParentIdPath() {
return parentIdPath;
}
public Path getPath() {
return path;
}
public long getSize() {
return size;
}
/** Readable during a task run only; the file is deleted when the flush ends. */
public byte[] getBytes() throws IOException {
return text != null ? text.getBytes(StandardCharsets.UTF_8) : Files.readAllBytes(path);
}
}