This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new bc1fbcaed7 TIKA-4681: unpack output is a Frictionless Data Package by
default (#3306)
bc1fbcaed7 is described below
commit bc1fbcaed769adaffeaab2c59167e27981792ee1
Author: Tim Allison <[email protected]>
AuthorDate: Wed Oct 7 14:31:18 2026 -0400
TIKA-4681: unpack output is a Frictionless Data Package by default (#3306)
---
.skills/users/file-forensics/SKILL.md | 20 +++--
.skills/users/file-to-markdown/SKILL.md | 10 +--
CHANGES.txt | 5 ++
.../advanced/integration-testing/tika-app.adoc | 21 +++---
.../advanced/integration-testing/tika-server.adoc | 4 +-
docs/modules/ROOT/pages/pipes/unpack-config.adoc | 80 ++++++++++++--------
docs/modules/ROOT/pages/using-tika/cli/index.adoc | 13 ++--
.../ROOT/pages/using-tika/server/index.adoc | 2 +-
.../src/main/java/org/apache/tika/cli/TikaCLI.java | 5 +-
.../test/java/org/apache/tika/cli/TikaCLITest.java | 87 +++++++++++++--------
.../org/apache/tika/async/cli/TikaAsyncCLI.java | 16 ++--
.../apache/tika/async/cli/AsyncProcessorTest.java | 5 +-
.../tika/pipes/core/extractor/UnpackConfig.java | 8 +-
.../extractor/UnpackConfigDeprecatedFlagsTest.java | 4 +-
.../tika/pipes/core/FrictionlessUnpackTest.java | 26 +++++++
.../org/apache/tika/pipes/core/UnpackModeTest.java | 53 ++++++++-----
.../server/core/resource/UnpackerResource.java | 27 +++----
.../tika/server/standard/ThumbnailPresetTest.java | 13 +++-
.../apache/tika/server/standard/TikaPipesTest.java | 27 ++++++-
.../server/standard/UnpackFrictionlessTest.java | 12 +--
...nlessTest.java => UnpackRegularConfigTest.java} | 88 +++++-----------------
.../standard/UnpackerResourceHandlerTest.java | 20 +++--
.../tika/server/standard/UnpackerResourceTest.java | 31 ++++----
23 files changed, 330 insertions(+), 247 deletions(-)
diff --git a/.skills/users/file-forensics/SKILL.md
b/.skills/users/file-forensics/SKILL.md
index 0bc3977554..0675d28b5d 100644
--- a/.skills/users/file-forensics/SKILL.md
+++ b/.skills/users/file-forensics/SKILL.md
@@ -106,7 +106,7 @@ java -jar tika-app.jar --config=file-forensics-config.json
-Z --extract-dir=evid
# mkdir -p evidence/suspect-embedded && unzip -q suspect-embedded.zip -d
evidence/suspect-embedded
jq 'map(del(."tk:content"))' suspect.rmeta.json > suspect.meta.json
-ls -lR evidence/suspect-embedded && sha256sum evidence/suspect-embedded/*/*
2>/dev/null || sha256sum evidence/suspect-embedded/*
+ls -lR evidence/suspect-embedded && sha256sum
evidence/suspect-embedded/*/unpacked/* 2>/dev/null || sha256sum
evidence/suspect-embedded/unpacked/*
```
`suspect.rmeta.json` is the full evidence record — a JSON array where entry
@@ -342,19 +342,23 @@ files is incomplete.
- Extracted files are **renumbered** (`00000001.jpg`, ...); original names
live in each rmeta entry's `tk:resource-name`, and `-z`/`-Z` writes a
sidecar `<name>.json` metadata dump for mapping numbers back to names.
-- Digest what you extracted (`sha256sum evidence/suspect-embedded/*/*`) so
- each artifact is pinned the same way the container file is.
+- The output is a Frictionless data package (Tika 4.2+): the files sit under
+ `<container>/unpacked/`, and `<container>/datapackage.json` already carries a
+ SHA-256 per file. Digest what you extracted
+ (`sha256sum evidence/suspect-embedded/*/unpacked/*`) and check it against the
+ manifest, so each artifact is pinned the same way the container file is.
- Mechanics and gotchas (Pipes-mode delay, chatty stderr) are in
`file-to-markdown`; the server-side equivalent is `/unpack`, which returns
- the embedded files as a zip over HTTP. **`/unpack` names differ from
- `-z`/`-Z`:** plain sequential names (`1.jpg`, `2.pdf`, ...) and **no
- sidecar JSON** — map names back via each rmeta entry's `tk:resource-name`
- yourself. Against the server started above (forensics config loaded):
+ the same package as one zip over HTTP. **`/unpack` names differ from
+ `-z`/`-Z`:** plain sequential names (`unpacked/1.jpg`, `unpacked/2.pdf`, ...)
+ and the rmeta rows live in the package's `metadata.json` rather than a
+ sidecar — map names back via each row's `tk:resource-name`. Against the
+ server started above (forensics config loaded):
```bash
curl -T suspect.file http://localhost:9998/unpack > suspect-embedded.zip
mkdir -p evidence/suspect-embedded && unzip -q suspect-embedded.zip -d
evidence/suspect-embedded
- sha256sum evidence/suspect-embedded/*
+ sha256sum evidence/suspect-embedded/unpacked/*
```
`/unpack/all` also includes the container's own text and metadata. Both
diff --git a/.skills/users/file-to-markdown/SKILL.md
b/.skills/users/file-to-markdown/SKILL.md
index 7ac6c83f6c..51c410007b 100644
--- a/.skills/users/file-to-markdown/SKILL.md
+++ b/.skills/users/file-to-markdown/SKILL.md
@@ -151,11 +151,11 @@ embedded content in: email attachments (`.eml`, `.msg`),
archive members
(`.zip`), PDF attachments, embedded objects/images in any office format
(an inline pasted picture counts), and so on.
-What lands on disk: a `<name>-embed/` directory of the embedded files
-**renumbered** (`00000001.jpg`, ...) — original names are not preserved on
-disk; they're in the rmeta output's `tk:resource-name` per entry, so keep
-the sibling `<name>.json` (an rmeta-shaped metadata dump `-z` also writes)
-if you need to map numbers back to names.
+What lands on disk: a `<name>/` Frictionless package — `datapackage.json`
+(SHA-256 per file), `metadata.json`, and the embedded files **renumbered**
+under `unpacked/` (`00000001.jpg`, ...). Original names are not preserved on
+disk; they are in each rmeta entry's `tk:resource-name`, so keep the sibling
+`<name>.json` (an rmeta-shaped dump `-z` also writes) to map numbers back.
`-z`/`-Z` route through Tika Pipes mode rather than the fast synchronous
path the other flags use — expect several seconds and a burst of
diff --git a/CHANGES.txt b/CHANGES.txt
index ba87b8d77f..dc63817378 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,10 @@
Release 4.2.0 - unreleased
+ * Unpack output is a Frictionless Data Package by default on every surface:
tika-server
+ /unpack, tika-app -z/-Z and pipes UNPACK mode, where it is one
<key>-frictionless.zip per
+ container unless outputMode is DIRECTORY. Set unpack-config.outputFormat
to REGULAR
+ (--unpack-format=REGULAR) for the pre-4.2 flat layout (TIKA-4681).
+
* Pipes plugins load only from the zips in plugin-roots; a plugin jar on
the classpath
needs -Dtika.plugins.classpath=true, which PipesForkParser sets on its
fork when it
has no plugins directory. Dev mode loads an exploded target/classes
(TIKA-4918).
diff --git a/docs/modules/ROOT/pages/advanced/integration-testing/tika-app.adoc
b/docs/modules/ROOT/pages/advanced/integration-testing/tika-app.adoc
index c1dc16d887..08b41395c1 100644
--- a/docs/modules/ROOT/pages/advanced/integration-testing/tika-app.adoc
+++ b/docs/modules/ROOT/pages/advanced/integration-testing/tika-app.adoc
@@ -328,27 +328,30 @@ java -jar tika-app.jar -a
--config=/tmp/tika-app-test/my-config.json
*Expected:* Same as Test 21 but using explicit async flag.
-=== Test 23: Unpack with Frictionless Format
+=== Test 23: Unpack (Frictionless package, the default)
[source,bash]
----
mkdir -p /tmp/tika-app-test/frictionless-out
-java -jar tika-app.jar -Z --extract-dir=/tmp/tika-app-test/frictionless-out
--unpack-format=FRICTIONLESS --unpack-include-metadata
test_recursive_embedded.docx
-ls /tmp/tika-app-test/frictionless-out
+java -jar tika-app.jar -Z --extract-dir=/tmp/tika-app-test/frictionless-out
test_recursive_embedded.docx
+ls -R /tmp/tika-app-test/frictionless-out
----
-*Expected:* Extracts embedded files in Frictionless data package format with
metadata.json.
+*Expected:* `test_recursive_embedded.docx.json` plus a
`test_recursive_embedded.docx/`
+directory holding `datapackage.json`, `metadata.json` and `unpacked/`.
-=== Test 24: Unpack to Directory (not zipped)
+=== Test 24: Unpack zipped, and the pre-4.2 flat layout
[source,bash]
----
-mkdir -p /tmp/tika-app-test/unpack-dir-out
-java -jar tika-app.jar -Z --extract-dir=/tmp/tika-app-test/unpack-dir-out
--unpack-mode=DIRECTORY test_recursive_embedded.docx
-ls -R /tmp/tika-app-test/unpack-dir-out
+mkdir -p /tmp/tika-app-test/unpack-zip-out
/tmp/tika-app-test/unpack-regular-out
+java -jar tika-app.jar -Z --extract-dir=/tmp/tika-app-test/unpack-zip-out
--unpack-mode=ZIPPED test_recursive_embedded.docx
+java -jar tika-app.jar -Z --extract-dir=/tmp/tika-app-test/unpack-regular-out
--unpack-format=REGULAR test_recursive_embedded.docx
+ls -R /tmp/tika-app-test/unpack-zip-out /tmp/tika-app-test/unpack-regular-out
----
-*Expected:* Extracts embedded files to directory structure instead of zipped.
+*Expected:* One `test_recursive_embedded.docx-frictionless.zip` in the first
directory;
+loose files under `test_recursive_embedded.docx-embed/` with no manifest in
the second.
=== Test 25: Batch with Multiple Workers
diff --git
a/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
b/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
index 5c18e980a8..414bb770f1 100644
--- a/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
+++ b/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
@@ -158,8 +158,8 @@ curl -s -X PUT -T test_recursive_embedded.docx
http://localhost:9998/unpack/all
unzip -l /tmp/unpack.zip
----
-*Expected:* ZIP file containing the extracted embedded files, a
`*.metadata.json` entry
-per file, and the original container document.
+*Expected:* A Frictionless package: `datapackage.json`, `metadata.json`, and
the embedded
+files plus the original container (`0.docx`) under `unpacked/`.
=== Test 13: GET /parsers
diff --git a/docs/modules/ROOT/pages/pipes/unpack-config.adoc
b/docs/modules/ROOT/pages/pipes/unpack-config.adoc
index af8560ae29..9ac0881ca7 100644
--- a/docs/modules/ROOT/pages/pipes/unpack-config.adoc
+++ b/docs/modules/ROOT/pages/pipes/unpack-config.adoc
@@ -50,13 +50,16 @@ live inside that block:
"parse-context": {
"unpack-config": {
"maxUnpackBytes": 104857600,
- "zipEmbeddedFiles": true
+ "outputMode": "ZIPPED"
}
}
}
----
-This extracts both metadata (like `RMETA` mode) and embedded document bytes.
+This extracts both metadata (like `RMETA` mode) and embedded document bytes.
Since 4.2 the
+output is a <<_frictionless_data_package_output,Frictionless Data Package>> on
every surface,
+one `<key>-frictionless.zip` per container unless `outputMode` is `DIRECTORY`;
+`"outputFormat": "REGULAR"` restores the pre-4.2 flat layout.
[NOTE]
====
@@ -95,7 +98,7 @@ immediately capped.
|`zipEmbeddedFiles`
|boolean
|`false`
-|Collect all embedded files into a single ZIP archive, emitted at the
container's emit key plus `-embedded.zip`.
+|`REGULAR` only: collect all embedded files into a single ZIP archive, emitted
at the container's emit key plus `-embedded.zip`. Frictionless packaging is
`outputMode`.
|`includeMetadata`
|boolean
@@ -133,9 +136,9 @@ immediately capped.
|Custom base path when `keyBaseStrategy=CUSTOM`.
|`outputFormat`
-|REGULAR, FRICTIONLESS
-|`REGULAR`
-|Output format for the ZIP archive. See
<<_frictionless_data_package_output,Frictionless Data Package Output>>.
+|FRICTIONLESS, REGULAR
+|`FRICTIONLESS`
+|`FRICTIONLESS` is a <<_frictionless_data_package_output,Frictionless Data
Package>>; `REGULAR` is the pre-4.2 flat layout (files at the root, no
manifest).
|`outputMode`
|ZIPPED, DIRECTORY
@@ -150,9 +153,24 @@ immediately capped.
== Examples
-=== ZIP Output with Metadata
+=== One package per container, with the original
-Collect all embedded files into a ZIP with metadata:
+[source,json]
+----
+{
+ "pipes": {
+ "parseMode": "UNPACK"
+ },
+ "parse-context": {
+ "unpack-config": {
+ "outputMode": "ZIPPED",
+ "includeOriginal": true
+ }
+ }
+}
+----
+
+=== The pre-4.2 flat ZIP with sidecars
[source,json]
----
@@ -162,6 +180,7 @@ Collect all embedded files into a ZIP with metadata:
},
"parse-context": {
"unpack-config": {
+ "outputFormat": "REGULAR",
"zipEmbeddedFiles": true,
"includeMetadata": true,
"includeOriginal": true
@@ -194,6 +213,9 @@ Collect all embedded files into a ZIP with metadata:
Produces names like `document-embed-00000001.pdf`. Under the `DEFAULT`
strategy the same
`zeroPadName`/`suffixStrategy` settings would produce
`<containerKey>-embed/00000001.pdf`.
+These keys are REGULAR output's; a Frictionless package sits at
`<containerKey>/` (or
+`<containerKey>-frictionless.zip`) and names its files
`unpacked/<id><suffix>`, so only
+`zeroPadName` and `suffixStrategy` show there.
== Suffix Strategies
@@ -225,9 +247,8 @@ The UNPACK mode can output files in
https://frictionlessdata.io/[Frictionless Da
a standard for packaging data files with their metadata. This format includes
a `datapackage.json`
manifest with file checksums and MIME types, making it easy to verify and
process extracted files.
-=== Enabling Frictionless Output
-
-Set `outputFormat` to `FRICTIONLESS` in your `unpack-config`:
+It is the default since 4.2 (TIKA-4681); 4.0 and 4.1 emitted the flat
`REGULAR` layout
+unless `outputFormat` said otherwise. To get that layout back:
[source,json]
----
@@ -237,8 +258,7 @@ Set `outputFormat` to `FRICTIONLESS` in your
`unpack-config`:
},
"parse-context": {
"unpack-config": {
- "outputFormat": "FRICTIONLESS",
- "includeMetadata": true
+ "outputFormat": "REGULAR"
}
}
}
@@ -246,7 +266,7 @@ Set `outputFormat` to `FRICTIONLESS` in your
`unpack-config`:
=== Output Structure
-When using Frictionless output format, the ZIP archive contains:
+A zipped package contains:
[source]
----
@@ -300,22 +320,23 @@ Naming the handler in both the path and a `config` part
is a `400`, as on `/tika
== What each surface produces
-The five knobs interact differently per surface. One default changes from
4.0.0: a
+The five knobs interact differently per surface. Two defaults changed since
4.0.0: a
Frictionless package carries `metadata.json` unless `includeMetadata` is
`false` (4.0.0 left
-it out unless `includeFullMetadata` was `true`). REGULAR output is as it was.
+it out unless `includeFullMetadata` was `true`), and since 4.2 Frictionless is
the format
+on every surface. REGULAR output is as it was.
[cols="1,2,2,2"]
|===
| |tika-server `/unpack` |tika-app `-z` / `-Z` |pipes JSON config
|format
-|REGULAR; set `outputFormat` in the server config for Frictionless
-|REGULAR; `--unpack-format=FRICTIONLESS`
+|Frictionless; `outputFormat: REGULAR` in the server config for the flat zip
+|Frictionless; `--unpack-format=REGULAR`
|`outputFormat`
|packaging
|always one zip (`ZIPPED` is pinned; an HTTP response is one body)
-|loose files; `DIRECTORY` when Frictionless is asked for without
`--unpack-mode`
+|loose files (`DIRECTORY`); `--unpack-mode=ZIPPED` for one zip
|`outputMode` governs Frictionless, `zipEmbeddedFiles` governs REGULAR
|depth
@@ -350,18 +371,18 @@ Output trees for `doc.docx` with three attachments:
[source]
----
-REGULAR, tika-server (one zip) REGULAR, tika-app -Z (loose)
+tika-server (one zip) tika-app -Z (loose)
+ datapackage.json out/doc.docx.json
+ metadata.json out/doc.docx/datapackage.json
+ unpacked/1.emf ... out/doc.docx/metadata.json
+ + with /all: unpacked/0.docx out/doc.docx/unpacked/1.emf ...
+
+REGULAR, tika-server (one zip) REGULAR, tika-app -Z
--unpack-format=REGULAR
1.emf out/doc.docx.json
2.zip out/doc.docx-embed/1.emf
3.bin out/doc.docx-embed/2.zip
+ with /all: 0.docx and one out/doc.docx-embed/3.bin
<name>.metadata.json per file
-
-FRICTIONLESS, tika-server (one zip) FRICTIONLESS, tika-app -Z
--unpack-format=FRICTIONLESS
- datapackage.json out/doc.docx.json
- metadata.json out/doc.docx/datapackage.json
- unpacked/1.emf ... out/doc.docx/metadata.json
- + with /all: unpacked/0.docx out/doc.docx/unpacked/1.emf ...
----
The top-level `doc.docx.json` on the command line is the normal pipes output
for the parse;
@@ -377,13 +398,12 @@ The `datapackage.json` file contains:
=== CLI Usage
-Extract files in Frictionless format using the CLI. The `-Z` flag turns on
recursive
-unpack (the Pipes-mode counterpart of standard-mode `-z`), and `-i`/`-o` are
the
-Pipes input/output directories:
+The `-Z` flag turns on recursive unpack (the Pipes-mode counterpart of
standard-mode `-z`),
+and `-i`/`-o` are the Pipes input/output directories:
[source,bash]
----
-java -jar tika-app.jar -Z --unpack-format=FRICTIONLESS -i /path/to/input -o
/path/to/output
+java -jar tika-app.jar -Z -i /path/to/input -o /path/to/output
----
NOTE: `-i` expects a directory of containers to unpack, not a single file. For
diff --git a/docs/modules/ROOT/pages/using-tika/cli/index.adoc
b/docs/modules/ROOT/pages/using-tika/cli/index.adoc
index 74fa7ad432..1835b30749 100644
--- a/docs/modules/ROOT/pages/using-tika/cli/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/cli/index.adoc
@@ -396,12 +396,12 @@ through every level. Both accept a single file or a
directory as input.
|Output directory for the extracted bytes; equivalent to `-o`.
|`--unpack-format=<format>`
-|Output format: `REGULAR` (default) or `FRICTIONLESS`.
+|Output format: `FRICTIONLESS` (default) or `REGULAR`, the pre-4.2 flat layout.
|`--unpack-mode=<mode>`
-|Output mode: `ZIPPED` (default) or `DIRECTORY`. With
`--unpack-format=FRICTIONLESS` and no
-mode, `DIRECTORY`: the package is laid out under `<out>/<input>/`
(`datapackage.json`,
-`unpacked/...`) rather than zipped.
+|Output mode: `DIRECTORY` (default) or `ZIPPED`. `DIRECTORY` lays the package
out under
+`<out>/<input>/` (`datapackage.json`, `metadata.json`, `unpacked/...`);
`ZIPPED` writes
+`<out>/<input>-frictionless.zip`.
|`--unpack-include-metadata`
|Metadata for every extracted file. A Frictionless package carries
`metadata.json` by default;
@@ -411,8 +411,9 @@ this adds a `.metadata.json` sidecar per file to REGULAR
zip output.
A `-c`/`--config` file's `unpack-config` is honoured and the flags above are
applied on top
of it. That is the only way to get the source document itself into the output
(`"unpack-config": {"includeOriginal": true}`); no flag does it. File naming
stays the
-CLI's own (`<input>-embed/00000001.<ext>`, detected suffixes) regardless of
the file's
-`suffixStrategy`, `zeroPadName`, `embeddedIdPrefix` or `keyBaseStrategy`. See
+CLI's own (`<input>/unpacked/00000001.<ext>`, or
`<input>-embed/00000001.<ext>` under
+`REGULAR`, detected suffixes, loose files unless `--unpack-mode=ZIPPED`)
regardless of the
+file's `suffixStrategy`, `zeroPadName`, `embeddedIdPrefix`, `keyBaseStrategy`
or `outputMode`. See
xref:pipes/unpack-config.adoc#_what_each_surface_produces[what each surface
produces].
=== Examples
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index 8b1676a6f8..bc27b79b17 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -577,7 +577,7 @@ before the exception are discarded. Known gap.
By default exception detail is returned in full. Stack traces and their
messages can contain the
spooled file's path, the source filename, and fragments of the document. That
detail appears in
`tk:exception:*` metadata on **successful** parses as well as in error bodies
— including inside
-the `*.metadata.json` entries of an `/unpack` zip.
+the `metadata.json` of an `/unpack` zip.
One config setting governs every one of those channels — the metadata values,
every error body
built from an exception (`422`, `500`; a `400` echoes the caller's own request
problem, `415` and
diff --git a/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
b/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
index d1c3d823d2..f52cdfab27 100644
--- a/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
+++ b/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
@@ -995,9 +995,8 @@ public class TikaCLI {
out.println(" --content-only Output only extracted
content (no JSON wrapper); implies --concatenate");
out.println(" --on-exists Behavior when an output
file exists: exception (default), replace, skip");
out.println(" -Z Recursively unpack all the
attachments, too");
- out.println(" --unpack-format=<format> Output format: REGULAR
(default) or FRICTIONLESS");
- out.println(" --unpack-mode=<mode> Output mode: ZIPPED
(default) or DIRECTORY;");
- out.println(" DIRECTORY when
--unpack-format=FRICTIONLESS and no mode is given");
+ out.println(" --unpack-format=<format> Output format:
FRICTIONLESS (default) or REGULAR");
+ out.println(" --unpack-mode=<mode> Output mode: DIRECTORY
(default: loose files) or ZIPPED");
out.println(" --unpack-include-metadata Metadata for every
extracted file. A Frictionless package");
out.println(" carries metadata.json by
default; this adds per-file");
out.println(" sidecars to REGULAR zip
output");
diff --git a/tika-app/src/test/java/org/apache/tika/cli/TikaCLITest.java
b/tika-app/src/test/java/org/apache/tika/cli/TikaCLITest.java
index 61efb3eab6..c105d56c82 100644
--- a/tika-app/src/test/java/org/apache/tika/cli/TikaCLITest.java
+++ b/tika-app/src/test/java/org/apache/tika/cli/TikaCLITest.java
@@ -371,10 +371,10 @@ public class TikaCLITest {
String[] expectedChildren = new String[]{
"testPDFPackage.pdf.json",
//the first two test that the default single file config is
working
- "testPDFPackage.pdf-embed/00000001.bin",
- "testPDFPackage.pdf-embed/00000002.jpg",
- "testPDFPackage.pdf-embed/00000003.pdf",
- "testPDFPackage.pdf-embed/00000004.pdf"};
+ "testPDFPackage.pdf/unpacked/00000001.bin",
+ "testPDFPackage.pdf/unpacked/00000002.jpg",
+ "testPDFPackage.pdf/unpacked/00000003.pdf",
+ "testPDFPackage.pdf/unpacked/00000004.pdf"};
testRecursiveUnpack("testPDFPackage.pdf", expectedChildren, 2);
}
@@ -383,15 +383,15 @@ public class TikaCLITest {
// TODO: The .bin extensions for embedded .msg files are wrong - they
should be .msg
// CONTENT_TYPE is not being set for embedded documents - see
~/Desktop/unpack-discussion/mime-todo.txt
String[] expectedChildren = new String[]{"testPST.pst.json",
- "testPST.pst-embed/00000007.bin",
- "testPST.pst-embed/00000001.bin",
- "testPST.pst-embed/00000008.bin",
- "testPST.pst-embed/00000004.bin",
- "testPST.pst-embed/00000003.bin",
- "testPST.pst-embed/00000002.bin",
- "testPST.pst-embed/00000005.bin",
- "testPST.pst-embed/00000009.docx",
- "testPST.pst-embed/00000006.bin"};
+ "testPST.pst/unpacked/00000007.bin",
+ "testPST.pst/unpacked/00000001.bin",
+ "testPST.pst/unpacked/00000008.bin",
+ "testPST.pst/unpacked/00000004.bin",
+ "testPST.pst/unpacked/00000003.bin",
+ "testPST.pst/unpacked/00000002.bin",
+ "testPST.pst/unpacked/00000005.bin",
+ "testPST.pst/unpacked/00000009.docx",
+ "testPST.pst/unpacked/00000006.bin"};
testRecursiveUnpack("testPST.pst", expectedChildren, 2);
try (Reader reader =
Files.newBufferedReader(extractDir.resolve("testPST.pst.json"))) {
List<Metadata> metadataList = JsonMetadataList.fromJson(reader);
@@ -449,30 +449,30 @@ public class TikaCLITest {
@Test
public void testExtractSimple() throws Exception {
- assertShallow("coffee.xls", 9, "coffee.xls.json",
"coffee.xls-embed/00000001.emf",
- "coffee.xls-embed/00000006.cdx",
"coffee.xls-embed/00000005.png");
+ assertShallow("coffee.xls", 11, "coffee.xls.json",
"coffee.xls/unpacked/00000001.emf",
+ "coffee.xls/unpacked/00000006.cdx",
"coffee.xls/unpacked/00000005.png");
}
@Test
public void testExtractAbsolute() throws Exception {
- assertShallow("testZip_absolutePath.zip", 3,
"testZip_absolutePath.zip.json",
- "testZip_absolutePath.zip-embed/00000001.bin");
+ assertShallow("testZip_absolutePath.zip", 5,
"testZip_absolutePath.zip.json",
+ "testZip_absolutePath.zip/unpacked/00000001.bin");
}
@Test
public void testExtractRelative() throws Exception {
- assertShallow("testZip_relative.zip", 2, "testZip_relative.zip.json");
+ assertShallow("testZip_relative.zip", 4, "testZip_relative.zip.json");
}
@Test
public void testExtractOverlapping() throws Exception {
// overlapping names are handled by numbering
- assertShallow("testZip_overlappingNames.zip", 3,
"testZip_overlappingNames.zip.json");
+ assertShallow("testZip_overlappingNames.zip", 5,
"testZip_overlappingNames.zip.json");
}
@Test
public void testExtract0x00() throws Exception {
- assertShallow("testZip_zeroByte.zip", 2, "testZip_zeroByte.zip.json");
+ assertShallow("testZip_zeroByte.zip", 4, "testZip_zeroByte.zip.json");
}
private void testRecursiveUnpack(String targetFile, String[]
expectedChildrenFileNames) throws Exception {
@@ -563,8 +563,8 @@ public class TikaCLITest {
Set<String> fileNames = shallowOutputOf("testWithSubdirs.zip");
assertTrue(fileNames.stream().anyMatch(f -> f.endsWith(".json")),
"Should have a .json metadata file, got: " + fileNames);
- assertTrue(fileNames.stream().anyMatch(f -> f.contains("-embed/")),
- "Should have extracted embedded files in -embed/ directory,
got: " + fileNames);
+ assertTrue(fileNames.stream().anyMatch(f -> f.contains("/unpacked/")),
+ "Should have extracted embedded files under unpacked/, got: "
+ fileNames);
}
@Test
@@ -579,7 +579,7 @@ public class TikaCLITest {
private static Set<String> shallowOutputOf(String inputName) {
Set<String> mine = new HashSet<>();
for (String f : shallowOutput) {
- if (f.equals(inputName + ".json") || f.startsWith(inputName +
"-embed/")) {
+ if (f.equals(inputName + ".json") || f.startsWith(inputName +
"/")) {
mine.add(f);
}
}
@@ -626,13 +626,14 @@ public class TikaCLITest {
// Should have extracted files in the specified directory, not current
dir
assertTrue(fileNames.stream().anyMatch(f -> f.endsWith(".json")),
"Should have a .json metadata file in extractDir, got: " +
fileNames);
- assertTrue(fileNames.stream().anyMatch(f -> f.contains("-embed/")),
+ assertTrue(fileNames.stream().anyMatch(f -> f.contains("/unpacked/")),
"Should have extracted embedded files in extractDir, got: " +
fileNames);
}
+ /** TIKA-4681: the default is a Frictionless package laid out as loose
files. */
@Test
- public void testFrictionlessWithoutModeIsADirectory() throws Exception {
- Set<String> fileNames = unpack("-Z", "--unpack-format=FRICTIONLESS");
+ public void testDefaultIsALooseFrictionlessPackage() throws Exception {
+ Set<String> fileNames = unpack("-Z");
assertTrue(fileNames.stream().anyMatch(f ->
f.endsWith("/datapackage.json")),
"package should be laid out as a directory, got: " +
fileNames);
assertTrue(fileNames.stream().anyMatch(f -> f.contains("/unpacked/")),
fileNames.toString());
@@ -642,9 +643,35 @@ public class TikaCLITest {
"a Frictionless package carries metadata.json by default: " +
fileNames);
}
+ @Test
+ public void testZippedModeWritesOnePackageZip() throws Exception {
+ Set<String> fileNames = unpack("-Z", "--unpack-mode=ZIPPED");
+
assertTrue(fileNames.contains("test_recursive_embedded.docx-frictionless.zip"),
fileNames.toString());
+ assertFalse(fileNames.stream().anyMatch(f -> f.contains("/unpacked/")),
+ "ZIPPED must not also write loose files: " + fileNames);
+ }
+
+ @Test
+ public void testRegularFormatIsTheFlatLayout() throws Exception {
+ Set<String> fileNames = unpack("-Z", "--unpack-format=REGULAR");
+ assertTrue(fileNames.stream().anyMatch(f ->
f.matches("test_recursive_embedded\\.docx-embed/0+1\\.[^/]+")),
+ fileNames.toString());
+ assertFalse(fileNames.stream().anyMatch(f ->
f.endsWith("datapackage.json") || f.contains("/unpacked/")),
+ "REGULAR has no manifest and no unpacked/: " + fileNames);
+ }
+
+ /** --unpack-mode is the CLI's packaging knob in both formats. */
+ @Test
+ public void testRegularZippedWritesOneFlatZip() throws Exception {
+ Set<String> fileNames = unpack("-Z", "--unpack-format=REGULAR",
"--unpack-mode=ZIPPED");
+
assertTrue(fileNames.contains("test_recursive_embedded.docx-embedded.zip"),
fileNames.toString());
+ assertFalse(fileNames.stream().anyMatch(f -> f.contains("-embed/")),
+ "ZIPPED must not also write loose files: " + fileNames);
+ }
+
@Test
public void testFrictionlessIncludeMetadata() throws Exception {
- Set<String> fileNames = unpack("-Z", "--unpack-format=FRICTIONLESS",
"--unpack-include-metadata");
+ Set<String> fileNames = unpack("-Z", "--unpack-include-metadata");
String metadataJson = fileNames.stream().filter(f ->
f.endsWith("/metadata.json")).findFirst()
.orElseThrow(() -> new AssertionError("no metadata.json in " +
fileNames));
String content = Files.readString(extractDir.resolve(metadataJson));
@@ -657,10 +684,10 @@ public class TikaCLITest {
@Test
public void testConfigFileUnpackConfigIsHonoured() throws Exception {
Set<String> fileNames = unpack("-Z", "--config=" + CONFIGS_DIR +
"/tika-config-unpack-original.json");
- assertTrue(fileNames.stream().anyMatch(f ->
f.matches(".*-embed/0+\\.[^/]+")),
+ assertTrue(fileNames.stream().anyMatch(f ->
f.matches(".*/unpacked/0+\\.[^/]+")),
"includeOriginal from -c should add the container as
00000000.<ext>: " + fileNames);
- assertTrue(fileNames.stream().anyMatch(f ->
f.matches(".*-embed/0+1\\.[^/]+")),
- "-c must not change the CLI's file naming: " + fileNames);
+ assertTrue(fileNames.stream().anyMatch(f ->
f.matches(".*/unpacked/0+1\\.[^/]+")),
+ "-c must not change the CLI's file naming or packaging: " +
fileNames);
}
private Set<String> unpack(String... flags) throws Exception {
diff --git
a/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaAsyncCLI.java
b/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaAsyncCLI.java
index 5b10176425..9311b1abc6 100644
---
a/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaAsyncCLI.java
+++
b/tika-pipes/tika-async-cli/src/main/java/org/apache/tika/async/cli/TikaAsyncCLI.java
@@ -85,10 +85,9 @@ public class TikaAsyncCLI {
options.addOption(null, "concatenate", false, "concatenate content
from all embedded documents into a single content field");
options.addOption(null, "content-only", false, "output only extracted
content (no metadata, no JSON wrapper); implies --concatenate");
options.addOption(null, "unpack-format", true,
- "output format for unpacking: REGULAR (default) or
FRICTIONLESS");
+ "output format for unpacking: FRICTIONLESS (default) or
REGULAR");
options.addOption(null, "unpack-mode", true,
- "output mode for unpacking: ZIPPED (default) or DIRECTORY;
DIRECTORY when "
- + "--unpack-format FRICTIONLESS is given with no
mode");
+ "output mode for unpacking: DIRECTORY (default: loose files)
or ZIPPED");
options.addOption(null, "unpack-include-metadata", false,
"metadata for every extracted file: a Frictionless package
carries metadata.json "
+ "by default; this adds per-file sidecars to REGULAR
zip output");
@@ -406,18 +405,17 @@ public class TikaAsyncCLI {
config.setZeroPadName(8);
config.setKeyBaseStrategy(UnpackConfig.KEY_BASE_STRATEGY.DEFAULT);
- // Command-line flags win over the file
+ // Packaging is the CLI's too: loose files unless --unpack-mode asks
for a zip
+ config.setOutputMode(UnpackConfig.OUTPUT_MODE.DIRECTORY);
+
if (asyncConfig.getUnpackFormat() != null) {
config.setOutputFormat(UnpackConfig.OUTPUT_FORMAT.valueOf(asyncConfig.getUnpackFormat()));
- // Loose files are what -z otherwise writes; a zip only when asked
for
- if (asyncConfig.getUnpackMode() == null
- && config.getOutputFormat() ==
UnpackConfig.OUTPUT_FORMAT.FRICTIONLESS) {
- config.setOutputMode(UnpackConfig.OUTPUT_MODE.DIRECTORY);
- }
}
if (asyncConfig.getUnpackMode() != null) {
config.setOutputMode(UnpackConfig.OUTPUT_MODE.valueOf(asyncConfig.getUnpackMode()));
}
+ // REGULAR packages on zipEmbeddedFiles, not outputMode
+ config.setZipEmbeddedFiles(config.getOutputMode() ==
UnpackConfig.OUTPUT_MODE.ZIPPED);
if (asyncConfig.isUnpackIncludeMetadata()) {
config.setIncludeMetadata(true);
}
diff --git
a/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/AsyncProcessorTest.java
b/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/AsyncProcessorTest.java
index a0eb2d8bb7..c74045da87 100644
---
a/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/AsyncProcessorTest.java
+++
b/tika-pipes/tika-async-cli/src/test/java/org/apache/tika/async/cli/AsyncProcessorTest.java
@@ -125,6 +125,7 @@ public class AsyncProcessorTest extends TikaTest {
unpackConfig.setEmitter("fse-bytes");
unpackConfig.setSuffixStrategy(UnpackConfig.SUFFIX_STRATEGY.NONE);
unpackConfig.setEmbeddedIdPrefix("-");
+ unpackConfig.setOutputMode(UnpackConfig.OUTPUT_MODE.DIRECTORY);
ParseContext parseContext = new ParseContext();
parseContext.set(ParseMode.class, ParseMode.UNPACK);
parseContext.set(UnpackConfig.class, unpackConfig);
@@ -144,10 +145,10 @@ public class AsyncProcessorTest extends TikaTest {
}
processor.close();
- String container =
Files.readString(bytesOutputDir.resolve("emit-1-embed/0"));
+ String container =
Files.readString(bytesOutputDir.resolve("emit-1/unpacked/0"));
assertContains("\"dc:creator\">Nikolai Lobachevsky", container);
- String xmlEmbedded =
Files.readString(bytesOutputDir.resolve("emit-1-embed/1"));
+ String xmlEmbedded =
Files.readString(bytesOutputDir.resolve("emit-1/unpacked/1"));
assertContains("name=\"dc:creator\"", xmlEmbedded);
assertContains(">embeddedAuthor</metadata>", xmlEmbedded);
diff --git
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/UnpackConfig.java
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/UnpackConfig.java
index c235daf1a0..bc02d5ce6a 100644
---
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/UnpackConfig.java
+++
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/UnpackConfig.java
@@ -83,7 +83,7 @@ public class UnpackConfig implements Serializable {
*/
public enum OUTPUT_FORMAT {
/**
- * Regular output - embedded files emitted individually or as simple
zip
+ * The pre-4.2 layout: embedded files emitted individually or as a
flat zip
*/
REGULAR,
/**
@@ -149,7 +149,7 @@ public class UnpackConfig implements Serializable {
private long maxUnpackBytes = DEFAULT_MAX_UNPACK_BYTES;
// Frictionless Data Package options
- private OUTPUT_FORMAT outputFormat = OUTPUT_FORMAT.REGULAR;
+ private OUTPUT_FORMAT outputFormat = OUTPUT_FORMAT.FRICTIONLESS;
private OUTPUT_MODE outputMode = OUTPUT_MODE.ZIPPED;
/**
@@ -321,8 +321,8 @@ public class UnpackConfig implements Serializable {
/**
* Get the output format for UNPACK mode.
- * REGULAR is the default (existing behavior).
- * FRICTIONLESS creates a Frictionless Data Package with datapackage.json
manifest.
+ * FRICTIONLESS (the default since 4.2) is a Frictionless Data Package
with a datapackage.json manifest.
+ * REGULAR is the pre-4.2 flat layout.
*/
public OUTPUT_FORMAT getOutputFormat() {
return outputFormat;
diff --git
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/extractor/UnpackConfigDeprecatedFlagsTest.java
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/extractor/UnpackConfigDeprecatedFlagsTest.java
index 0cdeeab749..45bf43a9f7 100644
---
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/extractor/UnpackConfigDeprecatedFlagsTest.java
+++
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/extractor/UnpackConfigDeprecatedFlagsTest.java
@@ -65,7 +65,9 @@ public class UnpackConfigDeprecatedFlagsTest {
UnpackConfig c = MAPPER.readValue("{\"outputFormat\":
\"FRICTIONLESS\"}", UnpackConfig.class);
assertTrue(c.writesMetadata());
assertFalse(MAPPER.readValue("{\"outputFormat\": \"REGULAR\"}",
UnpackConfig.class).writesMetadata());
- assertFalse(new UnpackConfig().writesMetadata());
+ // TIKA-4681: a fresh config is a Frictionless package
+ assertEquals(UnpackConfig.OUTPUT_FORMAT.FRICTIONLESS, new
UnpackConfig().getOutputFormat());
+ assertTrue(new UnpackConfig().writesMetadata());
UnpackConfig optOut = MAPPER.readValue(
"{\"outputFormat\": \"FRICTIONLESS\", \"includeMetadata\":
false}", UnpackConfig.class);
diff --git
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/FrictionlessUnpackTest.java
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/FrictionlessUnpackTest.java
index c64ab81bad..19e6d7cc0a 100644
---
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/FrictionlessUnpackTest.java
+++
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/FrictionlessUnpackTest.java
@@ -17,6 +17,7 @@
package org.apache.tika.pipes.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -126,6 +127,31 @@ public class FrictionlessUnpackTest {
return parseContext;
}
+ /** TIKA-4681: an UnpackConfig that says nothing about format is a
Frictionless package. */
+ @Test
+ public void testDefaultFormatIsFrictionless() throws Exception {
+ ParseContext parseContext = new ParseContext();
+ parseContext.set(ParseMode.class, ParseMode.UNPACK);
+ UnpackConfig unpackConfig = new UnpackConfig();
+ unpackConfig.setEmitter(EMITTER_NAME);
+ parseContext.set(UnpackConfig.class, unpackConfig);
+
+ PipesResult pipesResult = process("defaultFormat",
TEST_DOC_WITH_EMBEDDED, parseContext);
+ assertTrue(pipesResult.isSuccess(), "Status: " + pipesResult.status()
+ + ", Message: " + pipesResult.message());
+
+ Path outputDir = outputDir("defaultFormat");
+ List<String> names;
+ try (Stream<Path> files = Files.list(outputDir)) {
+ names = files.map(p -> p.getFileName().toString()).toList();
+ }
+ assertTrue(names.contains(TEST_DOC_WITH_EMBEDDED +
"-frictionless.zip"), names.toString());
+ assertFalse(names.contains(TEST_DOC_WITH_EMBEDDED + "-embedded.zip"),
names.toString());
+ Set<String> entries = zipEntries(onlyFrictionlessZip(outputDir));
+ assertTrue(entries.contains("datapackage.json"), entries.toString());
+ assertTrue(entries.contains("metadata.json"), "a package carries
metadata.json by default: " + entries);
+ }
+
@Test
public void testFrictionlessZippedOutput() throws Exception {
// ZIPPED structure, datapackage.json schema, and SHA256 correctness,
from one parse
diff --git
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/UnpackModeTest.java
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/UnpackModeTest.java
index fe82539b7f..755d10fe44 100644
---
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/UnpackModeTest.java
+++
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/UnpackModeTest.java
@@ -29,7 +29,9 @@ import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -120,10 +122,10 @@ public class UnpackModeTest {
for (Metadata m : metadataList) {
assertNotNull(m.get("Content-Type"));
}
- List<Path> embedded = embeddedFiles(outputDir, testDocWithEmbedded);
+ Map<String, Long> embedded = embeddedFiles(outputDir,
testDocWithEmbedded);
assertEquals(4, embedded.size(), "embedded bytes: " + embedded);
- for (Path p : embedded) {
- assertTrue(Files.size(p) > 0, p.toString());
+ for (Map.Entry<String, Long> e : embedded.entrySet()) {
+ assertTrue(e.getValue() > 0, e.toString());
}
}
@@ -168,8 +170,7 @@ public class UnpackModeTest {
assertTrue(pipesResult.isSuccess(),
"UNPACK with custom UnpackConfig should succeed. Status: " +
pipesResult.status());
- List<String> names = embeddedFiles(outputDir("custom"),
testDocWithEmbedded).stream()
- .map(f -> f.getFileName().toString()).toList();
+ Set<String> names = embeddedFiles(outputDir("custom"),
testDocWithEmbedded).keySet();
assertEquals(4, names.size(), names.toString());
for (String name : names) {
assertTrue(name.matches("\\d{8}\\..+"), "zero-padded with detected
suffix: " + name);
@@ -191,7 +192,7 @@ public class UnpackModeTest {
assertTrue(pipesResult.isSuccess(),
"UNPACK with includeOriginal should succeed. Status: " +
pipesResult.status());
- List<Path> embedded = embeddedFiles(outputDir("includeOriginal"),
testDocWithEmbedded);
+ Map<String, Long> embedded =
embeddedFiles(outputDir("includeOriginal"), testDocWithEmbedded);
assertEquals(5, embedded.size(), "4 embedded + the original: " +
embedded);
}
@@ -260,6 +261,7 @@ public class UnpackModeTest {
UnpackConfig unpackConfig = new UnpackConfig();
unpackConfig.setEmitter(emitterName);
+ unpackConfig.setOutputFormat(UnpackConfig.OUTPUT_FORMAT.REGULAR);
unpackConfig.setZipEmbeddedFiles(true);
unpackConfig.setSuffixStrategy(UnpackConfig.SUFFIX_STRATEGY.DETECTED);
parseContext.set(UnpackConfig.class, unpackConfig);
@@ -286,6 +288,7 @@ public class UnpackModeTest {
UnpackConfig unpackConfig = new UnpackConfig();
unpackConfig.setEmitter(emitterName);
+ unpackConfig.setOutputFormat(UnpackConfig.OUTPUT_FORMAT.REGULAR);
unpackConfig.setZipEmbeddedFiles(true);
unpackConfig.setIncludeMetadataInZip(true);
unpackConfig.setSuffixStrategy(UnpackConfig.SUFFIX_STRATEGY.DETECTED);
@@ -315,6 +318,7 @@ public class UnpackModeTest {
UnpackConfig unpackConfig = new UnpackConfig();
unpackConfig.setEmitter(emitterName);
+ unpackConfig.setOutputFormat(UnpackConfig.OUTPUT_FORMAT.REGULAR);
unpackConfig.setZipEmbeddedFiles(true);
unpackConfig.setIncludeOriginal(true);
unpackConfig.setSuffixStrategy(UnpackConfig.SUFFIX_STRATEGY.DETECTED);
@@ -338,6 +342,7 @@ public class UnpackModeTest {
UnpackConfig unpackConfig = new UnpackConfig();
unpackConfig.setEmitter(emitterName);
+ unpackConfig.setOutputFormat(UnpackConfig.OUTPUT_FORMAT.REGULAR);
unpackConfig.setZipEmbeddedFiles(true);
parseContext.set(UnpackConfig.class, unpackConfig);
@@ -359,6 +364,7 @@ public class UnpackModeTest {
UnpackConfig unpackConfig = new UnpackConfig();
unpackConfig.setEmitter(emitterName);
unpackConfig.setMaxUnpackBytes(10L);
+ unpackConfig.setOutputMode(UnpackConfig.OUTPUT_MODE.DIRECTORY);
parseContext.set(UnpackConfig.class, unpackConfig);
PipesResult pipesResult = process("limited", testDocWithEmbedded,
testDocWithEmbedded + "-limited",
@@ -371,7 +377,7 @@ public class UnpackModeTest {
try (Stream<Path> files = Files.walk(outputDir("limited"))) {
totalBytesWritten = files
.filter(Files::isRegularFile)
- .filter(p -> !p.toString().endsWith(".json"))
+ .filter(p -> p.toString().replace('\\',
'/').contains("/unpacked/"))
.mapToLong(p -> {
try {
return Files.size(p);
@@ -403,10 +409,10 @@ public class UnpackModeTest {
assertTrue(pipesResult.isSuccess(),
"UNPACK with default maxUnpackBytes should succeed. Status: "
+ pipesResult.status());
- List<Path> embedded = embeddedFiles(outputDir("default"),
testDocWithEmbedded + "-default");
+ Map<String, Long> embedded = embeddedFiles(outputDir("default"),
testDocWithEmbedded + "-default");
assertEquals(4, embedded.size(), embedded.toString());
- for (Path p : embedded) {
- assertEquals(146, Files.size(p), p.toString());
+ for (Map.Entry<String, Long> e : embedded.entrySet()) {
+ assertEquals(146, e.getValue(), e.toString());
}
}
@@ -426,10 +432,10 @@ public class UnpackModeTest {
assertTrue(pipesResult.isSuccess(),
"UNPACK with unlimited maxUnpackBytes should succeed. Status:
" + pipesResult.status());
- List<Path> embedded = embeddedFiles(outputDir("unlimited"),
testDocWithEmbedded + "-unlimited");
+ Map<String, Long> embedded = embeddedFiles(outputDir("unlimited"),
testDocWithEmbedded + "-unlimited");
assertEquals(4, embedded.size(), embedded.toString());
- for (Path p : embedded) {
- assertEquals(146, Files.size(p), p.toString());
+ for (Map.Entry<String, Long> e : embedded.entrySet()) {
+ assertEquals(146, e.getValue(), e.toString());
}
}
@@ -463,13 +469,22 @@ public class UnpackModeTest {
}
}
- private static List<Path> embeddedFiles(Path outputDir, String emitKey)
throws IOException {
- Path dir = outputDir.resolve(emitKey + "-embed");
- if (!Files.isDirectory(dir)) {
- return List.of();
+ /** The default package is a ZIPPED Frictionless one: entry name under
unpacked/ -> size. */
+ private static Map<String, Long> embeddedFiles(Path outputDir, String
emitKey) throws IOException {
+ Path zipFile = outputDir.resolve(emitKey + "-frictionless.zip");
+ Map<String, Long> embedded = new TreeMap<>();
+ if (!Files.isRegularFile(zipFile)) {
+ return embedded;
}
- try (Stream<Path> files = Files.list(dir)) {
- return files.sorted().toList();
+ try (ZipFile zip = new ZipFile(zipFile.toFile())) {
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (entry.getName().startsWith("unpacked/") &&
!entry.isDirectory()) {
+
embedded.put(entry.getName().substring("unpacked/".length()), entry.getSize());
+ }
+ }
}
+ return embedded;
}
}
diff --git
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/UnpackerResource.java
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/UnpackerResource.java
index 321d9673ab..cdf96de978 100644
---
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/UnpackerResource.java
+++
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/UnpackerResource.java
@@ -89,23 +89,10 @@ import org.apache.tika.pipes.core.extractor.UnpackConfig;
* }
* </pre>
* <p>
- * <b>Frictionless Data Package Format:</b>
+ * <b>Output format:</b>
* <p>
- * To receive output in Frictionless Data Package format (with
datapackage.json manifest,
- * SHA256 hashes, and files in unpacked/ subdirectory), use:
- * <pre>
- * {
- * "parse-context": {
- * "unpack-config": {
- * "outputFormat": "FRICTIONLESS",
- * "outputMode": "ZIPPED",
- * "includeMetadata": true
- * }
- * }
- * }
- * </pre>
- * <p>
- * The Frictionless zip structure:
+ * The response is a Frictionless Data Package (datapackage.json manifest with
SHA256 hashes,
+ * files under unpacked/):
* <pre>
* output.zip
* ├── datapackage.json # Manifest with file list, SHA256 hashes,
mimetypes
@@ -115,6 +102,14 @@ import org.apache.tika.pipes.core.extractor.UnpackConfig;
* ├── 00000002.png
* └── ...
* </pre>
+ * The pre-4.2 flat zip (files at the root, no manifest) is still available:
+ * <pre>
+ * {
+ * "parse-context": {
+ * "unpack-config": { "outputFormat": "REGULAR" }
+ * }
+ * }
+ * </pre>
* <p>
* <b>Breaking Changes from Pre-4.0:</b>
* <ul>
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ThumbnailPresetTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ThumbnailPresetTest.java
index 7ea8881665..4ac707aa42 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ThumbnailPresetTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/ThumbnailPresetTest.java
@@ -29,6 +29,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
@@ -163,10 +164,20 @@ public class ThumbnailPresetTest extends CXFTestBase {
.getStatus(), inner);
}
+ /**
+ * @return the embedded documents of the Frictionless package, without its
+ * datapackage.json and metadata.json
+ */
private Map<String, byte[]> unpack(String path, String file) throws
IOException {
Response response = put(path, fixture(file));
assertEquals(200, response.getStatus(), file);
- return readZipArchiveBytes((InputStream) response.getEntity());
+ Map<String, byte[]> unpacked = new LinkedHashMap<>();
+ readZipArchiveBytes((InputStream) response.getEntity()).forEach((name,
bytes) -> {
+ if (name.startsWith("unpacked/")) {
+ unpacked.put(name, bytes);
+ }
+ });
+ return unpacked;
}
private Response put(String path, InputStream body) {
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaPipesTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaPipesTest.java
index a3cf4bb072..f544e50c54 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaPipesTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaPipesTest.java
@@ -18,6 +18,7 @@ package org.apache.tika.server.standard;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -32,9 +33,14 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import jakarta.ws.rs.core.Response;
import org.apache.commons.io.FileUtils;
@@ -320,9 +326,26 @@ public class TikaPipesTest extends CXFTestBase {
assertContains("When in the Course", metadataList
.get(6)
.get(TikaCoreProperties.TIKA_CONTENT));
- Map<String, Long> expected = loadExpected();
+ // TIKA-4681: the bytes emitter writes one Frictionless package, the
embedded
+ // documents under unpacked/ in it
Map<String, Long> byteFileNames = getFileNames(outputBytesDir);
- assertEquals(expected, byteFileNames);
+ assertEquals(Set.of(TEST_RECURSIVE_DOC + "-frictionless.zip"),
byteFileNames.keySet());
+ List<Long> unpackedSizes = new ArrayList<>();
+ try (ZipFile zip = new ZipFile(
+ outputBytesDir.resolve(TEST_RECURSIVE_DOC +
"-frictionless.zip").toFile())) {
+ assertNotNull(zip.getEntry("datapackage.json"));
+ assertNotNull(zip.getEntry("metadata.json"));
+ for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
+ ZipEntry entry = e.nextElement();
+ if (entry.getName().startsWith("unpacked/")) {
+ unpackedSizes.add(entry.getSize());
+ }
+ }
+ }
+ List<Long> expectedSizes = new ArrayList<>(loadExpected().values());
+ Collections.sort(expectedSizes);
+ Collections.sort(unpackedSizes);
+ assertEquals(expectedSizes, unpackedSizes);
}
private Map<String, Long> loadExpected() {
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
index e05b658021..7c671b29e2 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
@@ -50,7 +50,7 @@ import org.apache.tika.server.core.resource.UnpackerResource;
import org.apache.tika.server.core.writer.TarWriter;
import org.apache.tika.server.core.writer.ZipWriter;
-/** /unpack under a server-level Frictionless config, with no per-request
config. */
+/** /unpack with no unpack-config anywhere: a Frictionless package since 4.2
(TIKA-4681). */
public class UnpackFrictionlessTest extends CXFTestBase {
private static final String TEST_DOC =
"test-documents/test_recursive_embedded.docx";
@@ -58,10 +58,7 @@ public class UnpackFrictionlessTest extends CXFTestBase {
private static final String SERVER_CONFIG = """
{
- "parsers": [ { "default-parser": {} } ],
- "parse-context": {
- "unpack-config": { "outputFormat": "FRICTIONLESS",
"outputMode": "ZIPPED" }
- }
+ "parsers": [ { "default-parser": {} } ]
}
""";
@@ -113,11 +110,14 @@ public class UnpackFrictionlessTest extends CXFTestBase {
}
@Test
- public void testServerConfigMakesUnpackFrictionless() throws Exception {
+ public void testDefaultIsAFrictionlessPackage() throws Exception {
Map<String, byte[]> entries = unpack("/unpack");
assertTrue(entries.containsKey("datapackage.json"),
entries.keySet().toString());
assertTrue(entries.keySet().stream().anyMatch(k ->
k.startsWith("unpacked/")),
entries.keySet().toString());
+ assertTrue(entries.keySet().stream().allMatch(k ->
k.startsWith("unpacked/")
+ || k.equals("datapackage.json") ||
k.equals("metadata.json")),
+ "nothing but the package at the root: " + entries.keySet());
}
/** A Frictionless package carries metadata.json by default; /all adds
only the original. */
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackRegularConfigTest.java
similarity index 53%
copy from
tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
copy to
tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackRegularConfigTest.java
index e05b658021..07bf63c0aa 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackFrictionlessTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackRegularConfigTest.java
@@ -24,10 +24,10 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -37,9 +37,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.core.Response;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.jaxrs.ext.multipart.Attachment;
-import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
-import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.junit.jupiter.api.Test;
@@ -50,8 +47,8 @@ import org.apache.tika.server.core.resource.UnpackerResource;
import org.apache.tika.server.core.writer.TarWriter;
import org.apache.tika.server.core.writer.ZipWriter;
-/** /unpack under a server-level Frictionless config, with no per-request
config. */
-public class UnpackFrictionlessTest extends CXFTestBase {
+/** A server-level unpack-config.outputFormat of REGULAR restores the pre-4.2
flat zip. */
+public class UnpackRegularConfigTest extends CXFTestBase {
private static final String TEST_DOC =
"test-documents/test_recursive_embedded.docx";
private static final ObjectMapper MAPPER = new ObjectMapper();
@@ -60,7 +57,7 @@ public class UnpackFrictionlessTest extends CXFTestBase {
{
"parsers": [ { "default-parser": {} } ],
"parse-context": {
- "unpack-config": { "outputFormat": "FRICTIONLESS",
"outputMode": "ZIPPED" }
+ "unpack-config": { "outputFormat": "REGULAR" }
}
}
""";
@@ -95,7 +92,7 @@ public class UnpackFrictionlessTest extends CXFTestBase {
@Override
protected InputStream getPipesConfigInputStream() throws IOException {
- unpackTempDir = sharedUnpackDir();
+ unpackTempDir = Files.createTempDirectory("tika-unpack-regular-");
Map<String, Object> replacements = new HashMap<>();
replacements.put("UNPACK_EMITTER_BASE_PATH",
unpackTempDir.toAbsolutePath().toString());
replacements.put("PLUGINS_PATHS",
@@ -113,74 +110,23 @@ public class UnpackFrictionlessTest extends CXFTestBase {
}
@Test
- public void testServerConfigMakesUnpackFrictionless() throws Exception {
+ public void testServerConfigRestoresTheFlatLayout() throws Exception {
Map<String, byte[]> entries = unpack("/unpack");
- assertTrue(entries.containsKey("datapackage.json"),
entries.keySet().toString());
- assertTrue(entries.keySet().stream().anyMatch(k ->
k.startsWith("unpacked/")),
- entries.keySet().toString());
- }
-
- /** A Frictionless package carries metadata.json by default; /all adds
only the original. */
- @Test
- public void testPlainUnpackHasMetadataJsonByDefault() throws Exception {
- Map<String, byte[]> entries = unpack("/unpack");
- assertTrue(entries.containsKey("metadata.json"),
entries.keySet().toString());
- assertFalse(entries.keySet().stream().anyMatch(k ->
k.startsWith("unpacked/0.")),
- "plain /unpack must not pack the container's bytes: " +
entries.keySet());
- }
-
- /** DIRECTORY from a request contradicts the one-body transport: 400, not
a silent pin. */
- @Test
- public void testDirectoryModeInRequestIs400() throws Exception {
- ContentDisposition cd = new ContentDisposition(
- "form-data; name=\"file\";
filename=\"test_recursive_embedded.docx\"");
- Attachment fileAtt = new Attachment("file",
ClassLoader.getSystemResourceAsStream(TEST_DOC), cd);
- Attachment configAtt = new Attachment("config", "application/json",
new ByteArrayInputStream(
- "{\"unpack-config\": {\"outputMode\":
\"DIRECTORY\"}}".getBytes(StandardCharsets.UTF_8)));
- Response response = WebClient.create(endPoint + "/unpack")
- .type("multipart/form-data")
- .accept("application/zip")
- .post(new MultipartBody(Arrays.asList(fileAtt, configAtt)));
- assertEquals(400, response.getStatus());
+ assertFalse(entries.containsKey("datapackage.json"),
entries.keySet().toString());
+ assertFalse(entries.containsKey("metadata.json"),
entries.keySet().toString());
+ assertTrue(entries.keySet().stream().noneMatch(k -> k.contains("/")),
+ "REGULAR puts every file at the zip root: " +
entries.keySet());
+ assertFalse(entries.isEmpty());
}
+ /** Under REGULAR, /all is where the per-file sidecars and the original
come from. */
@Test
- public void testUnpackAllCarriesMetadataAndTheOriginal() throws Exception {
+ public void testUnpackAllAddsSidecarsAndTheOriginal() throws Exception {
Map<String, byte[]> entries = unpack("/unpack/all");
- assertTrue(entries.containsKey("metadata.json"),
entries.keySet().toString());
- assertTrue(entries.keySet().stream().anyMatch(k ->
k.startsWith("unpacked/0.")),
- "includeOriginal should add the container: " +
entries.keySet());
- }
-
- /** The RMETA list's row 0 is the container -- its own metadata and its
own text. */
- @Test
- public void testMetadataJsonCarriesTheContainerRow() throws Exception {
- Map<String, byte[]> entries = unpack("/unpack");
- JsonNode rows = MAPPER.readTree(entries.get("metadata.json"));
- assertTrue(rows.isArray() && rows.size() > 1, "expected an RMETA list,
got: " + rows);
-
- JsonNode container = rows.get(0);
- assertEquals("0", container.path("tk:embedded-depth").asText(),
- "row 0 should be the container: " + container);
- assertTrue(container.hasNonNull("tk:content"),
- "the container row should carry its own extracted text: " +
container);
- assertTrue(container.hasNonNull("dcterms:created"),
- "the container row should carry its own metadata: " +
container);
- }
-
- /** Every row can be joined to its file: id, tree position and logical
path all travel. */
- @Test
- public void testRowsCarryEmbeddedPathData() throws Exception {
- Map<String, byte[]> entries = unpack("/unpack/all");
- JsonNode rows = MAPPER.readTree(entries.get("metadata.json"));
- JsonNode embedded = rows.get(1);
- for (String key : new String[]{"tk:embedded-id", "tk:embedded-id-path",
- "tk:embedded-resource-path", "tk:resource-name"}) {
- assertTrue(embedded.hasNonNull(key), key + " missing from: " +
embedded);
- }
- String id = embedded.get("tk:embedded-id").asText();
- assertTrue(entries.keySet().stream().anyMatch(k ->
k.startsWith("unpacked/" + id + ".")),
- "row's tk:embedded-id should name a packed file: " +
entries.keySet());
+ assertTrue(entries.keySet().stream().anyMatch(k ->
k.endsWith(".metadata.json")),
+ entries.keySet().toString());
+ assertTrue(entries.keySet().stream().anyMatch(k -> k.startsWith("0.")),
+ "includeOriginal should add the container at the root: " +
entries.keySet());
}
private Map<String, byte[]> unpack(String path) throws Exception {
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceHandlerTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceHandlerTest.java
index 699edf794b..c1169f14ee 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceHandlerTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceHandlerTest.java
@@ -54,11 +54,21 @@ import org.apache.tika.server.core.writer.ZipWriter;
/**
* /unpack has no format segment, so the config part is the only place a
handler is named --
* and it reaches the RMETA list written to metadata.json.
+ * Per-file sidecars exist only in the REGULAR layout, so the server config
pins it; the
+ * metadata.json probes ask for FRICTIONLESS explicitly.
*/
public class UnpackerResourceHandlerTest extends CXFTestBase {
private static final String TEST_DOC =
"test-documents/test_recursive_embedded.docx";
private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final String SERVER_CONFIG = """
+ {
+ "parsers": [ { "default-parser": {} } ],
+ "parse-context": {
+ "unpack-config": { "outputFormat": "REGULAR" }
+ }
+ }
+ """;
private static final String FRICTIONLESS =
"\"unpack-config\": {\"outputFormat\": \"FRICTIONLESS\",
\"outputMode\": \"ZIPPED\", "
+ "\"includeFullMetadata\": true}";
@@ -88,7 +98,7 @@ public class UnpackerResourceHandlerTest extends CXFTestBase {
@Override
protected InputStream getTikaConfigInputStream() throws IOException {
- return
this.getClass().getResourceAsStream("/configs/tika-config-unpacker.json");
+ return new
ByteArrayInputStream(SERVER_CONFIG.getBytes(StandardCharsets.UTF_8));
}
@Override
@@ -171,10 +181,10 @@ public class UnpackerResourceHandlerTest extends
CXFTestBase {
/** includeMetadataInZip writes a sidecar per extracted file; it carries
tk:content too. */
@Test
public void testPerFileSidecarsFollowTheConfigPartHandler() throws
Exception {
- assertTrue(perFileSidecar("{\"unpack-config\":
{\"includeMetadataInZip\": true}}")
+ assertTrue(perFileSidecar("{\"unpack-config\": {\"outputFormat\":
\"REGULAR\", \"includeMetadataInZip\": true}}")
.contains("\"tk:content-handler-type\":\"MARKDOWN\""));
- String xml = perFileSidecar("{\"unpack-config\":
{\"includeMetadataInZip\": true}, "
+ String xml = perFileSidecar("{\"unpack-config\": {\"outputFormat\":
\"REGULAR\", \"includeMetadataInZip\": true}, "
+ "\"basic-content-handler-factory\": {\"type\": \"XML\"}}");
assertTrue(xml.contains("\"tk:content-handler-type\":\"XML\""), xml);
assertTrue(xml.contains("<html xmlns="), xml);
@@ -183,8 +193,8 @@ public class UnpackerResourceHandlerTest extends
CXFTestBase {
/** IGNORE is the opt-out: sidecars keep their metadata but carry no
extracted text. */
@Test
public void testIgnoreHandlerLeavesSidecarsWithoutContent() throws
Exception {
- String withText = "{\"unpack-config\": {\"includeMetadataInZip\":
true}}";
- String ignore = "{\"unpack-config\": {\"includeMetadataInZip\": true},
"
+ String withText = "{\"unpack-config\": {\"outputFormat\": \"REGULAR\",
\"includeMetadataInZip\": true}}";
+ String ignore = "{\"unpack-config\": {\"outputFormat\": \"REGULAR\",
\"includeMetadataInZip\": true}, "
+ "\"basic-content-handler-factory\": {\"type\": \"IGNORE\"}}";
assertTrue(sidecarsWithContent(withText) > 0, "baseline should carry
tk:content");
diff --git
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
index 00006e6e2d..26ffa7680f 100644
---
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
+++
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
@@ -172,11 +172,8 @@ public class UnpackerResourceTest extends CXFTestBase {
.toList();
assertEquals(2, wavFiles.size(), "Should have 2 WAV files");
- // With saveAll=true, metadata JSON files should be included
- List<String> metadataFiles = data.keySet().stream()
- .filter(k -> k.endsWith(".metadata.json"))
- .toList();
- assertTrue(metadataFiles.size() >= 2, "Should have metadata JSON files
for each embedded doc");
+ assertTrue(data.containsKey("metadata.json"),
+ "/all carries the parse as metadata.json. Entries: " +
data.keySet());
}
@Test
@@ -271,7 +268,6 @@ public class UnpackerResourceTest extends CXFTestBase {
@Test
public void testMetadataJsonIncluded() throws Exception {
- // Test that /unpack/all includes metadata JSON files
Response response = WebClient
.create(endPoint + ALL_PATH)
.type(APPLICATION_MSWORD)
@@ -280,14 +276,9 @@ public class UnpackerResourceTest extends CXFTestBase {
Map<String, byte[]> data = readZipArchiveBytes((InputStream)
response.getEntity());
- // Should have metadata JSON files
- List<String> metadataFiles = data.keySet().stream()
- .filter(k -> k.endsWith(".metadata.json"))
- .toList();
- assertFalse(metadataFiles.isEmpty(), "Should have metadata JSON
files");
-
- // Verify the JSON contains expected metadata fields
- String metadataJson = new String(data.get(metadataFiles.get(0)),
StandardCharsets.UTF_8);
+ byte[] metadata = data.get("metadata.json");
+ assertNotNull(metadata, "Should have metadata.json. Entries: " +
data.keySet());
+ String metadataJson = new String(metadata, StandardCharsets.UTF_8);
assertTrue(metadataJson.contains("Content-Type"), "Metadata JSON
should contain Content-Type");
}
@@ -424,8 +415,9 @@ public class UnpackerResourceTest extends CXFTestBase {
assertEquals(200, response.getStatus());
Map<String, String> data = readZipArchive((InputStream)
response.getEntity());
- // With maxCount=1, should only have 1 embedded document
- assertEquals(1, data.size(), "Should have exactly 1 embedded document
with maxCount=1");
+ long unpacked = data.keySet().stream().filter(k ->
k.startsWith("unpacked/")).count();
+ assertEquals(1, unpacked, "Should have exactly 1 embedded document
with maxCount=1. Entries: "
+ + data.keySet());
}
/**
@@ -595,15 +587,20 @@ public class UnpackerResourceTest extends CXFTestBase {
*/
@Test
public void testRegularAllContainerAppearsOnce() throws Exception {
+ String configJson = """
+ { "parse-context": { "unpack-config": { "outputFormat":
"REGULAR" } } }
+ """;
ContentDisposition fileCd = new ContentDisposition("form-data;
name=\"file\"; filename=\"Doc1_ole.doc\"");
Attachment fileAtt = new Attachment("file",
ClassLoader.getSystemResourceAsStream(TEST_DOC_WAV), fileCd);
+ Attachment configAtt = new Attachment("config", "application/json",
+ new
ByteArrayInputStream(configJson.getBytes(StandardCharsets.UTF_8)));
Response response = WebClient
.create(endPoint + ALL_PATH)
.type("multipart/form-data")
.accept("application/zip")
- .post(new MultipartBody(Arrays.asList(fileAtt)));
+ .post(new MultipartBody(Arrays.asList(fileAtt, configAtt)));
assertEquals(200, response.getStatus());
Map<String, String> data = readZipArchive((InputStream)
response.getEntity());