This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch TIKA-4809-stage-2
in repository https://gitbox.apache.org/repos/asf/tika.git

commit 519963cdbcac734cf50d008085e5e1319cbba8cd
Author: tallison <[email protected]>
AuthorDate: Fri Aug 7 15:18:34 2026 -0400

    TIKA-4809: Migrate /meta onto the shared pipes-backed PipesParser
---
 .../server/core/resource/MetadataResource.java     | 107 ++++++++++-----------
 .../apache/tika/server/core/StackTraceTest.java    |  13 ++-
 .../standard/resource/XMPMetadataResource.java     |   6 +-
 .../tika/server/standard/MetadataResourceTest.java |  24 +++--
 4 files changed, 76 insertions(+), 74 deletions(-)

diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
index ec0d9bc46f..1fbc0bffdb 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java
@@ -18,7 +18,6 @@ package org.apache.tika.server.core.resource;
 
 import static org.apache.tika.server.core.resource.TikaResource.fillMetadata;
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
@@ -37,13 +36,16 @@ import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.tika.exception.TikaConfigException;
-import org.apache.tika.extractor.DocumentSelector;
+import org.apache.tika.config.EmbeddedLimits;
+import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
-import org.apache.tika.language.detect.LanguageHandler;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.Parser;
+import org.apache.tika.pipes.api.ParseMode;
+import org.apache.tika.sax.BasicContentHandlerFactory;
+import org.apache.tika.sax.ContentHandlerFactory;
+import org.apache.tika.server.core.TikaServerParseException;
 
 
 @Path("/meta")
@@ -63,8 +65,9 @@ public class MetadataResource {
     public Response getMetadataFromMultipart(Attachment att, @Context UriInfo 
info) throws Exception {
         ParseContext context = tikaResource.createParseContext();
         try (TikaInputStream tis = 
TikaInputStream.get(att.getObject(InputStream.class))) {
+            tis.getPath(); // Spool to temp file for pipes-based parsing
             return Response
-                    .ok(parseMetadata(tis, Metadata.newInstance(context), 
att.getHeaders(), info))
+                    .ok(parseMetadata(tis, Metadata.newInstance(context), 
att.getHeaders(), context))
                     .build();
         }
     }
@@ -79,25 +82,14 @@ public class MetadataResource {
     @Path("config")
     public Response getMetadataWithConfig(
             List<Attachment> attachments,
-            @Context HttpHeaders httpHeaders,
-            @Context UriInfo info) throws Exception {
+            @Context HttpHeaders httpHeaders) throws Exception {
 
         // Load default context from config, then overlay with request config
         ParseContext context = tikaResource.createParseContext();
         Metadata metadata = Metadata.newInstance(context);
         try (TikaInputStream tis = 
tikaResource.setupMultipartConfig(attachments, metadata, context)) {
-            // No need to parse embedded docs for metadata-only extraction
-            context.set(DocumentSelector.class, metadata1 -> false);
-
-            Parser parser = tikaResource.createParser();
             TikaResource.logRequest(LOG, "/meta/config", metadata);
-            tikaResource.parse(parser, LOG, info.getPath(), tis, new 
LanguageHandler() {
-                public void endDocument() {
-                    metadata.set("language", getLanguage().getLanguage());
-                }
-            }, metadata, context);
-
-            return Response.ok(metadata).build();
+            return Response.ok(parseMetadata(tis, metadata, 
httpHeaders.getRequestHeaders(), context)).build();
         }
     }
 
@@ -107,19 +99,19 @@ public class MetadataResource {
         ParseContext context = tikaResource.createParseContext();
         Metadata metadata = Metadata.newInstance(context);
         try (TikaInputStream tis = TikaInputStream.get(is)) {
+            tis.getPath(); // Spool to temp file for pipes-based parsing
             return Response
-                    .ok(parseMetadata(tis, metadata, 
httpHeaders.getRequestHeaders(), info))
+                    .ok(parseMetadata(tis, metadata, 
httpHeaders.getRequestHeaders(), context))
                     .build();
         }
     }
 
     /**
-     * Get a specific metadata field. If the input stream cannot be parsed, 
but a
-     * value was found for the given metadata field, then the value of the 
field
-     * is returned as part of a 200 OK response; otherwise a
-     * {@link javax.ws.rs.core.Response.Status#BAD_REQUEST} is generated. If 
the stream
-     * was successfully parsed but the specific metadata field was not found, 
then a
-     * {@link javax.ws.rs.core.Response.Status#NOT_FOUND} is returned.
+     * Get a specific metadata field. If the document parses successfully but 
the
+     * specific metadata field was not found, a
+     * {@link javax.ws.rs.core.Response.Status#NOT_FOUND} is returned. Unlike 
the other
+     * /meta endpoints, a bare field value has no envelope to embed a 
container-level
+     * exception in, so that case is thrown (422) instead.
      * <p/>
      * Note that this method handles multivalue fields and returns possibly 
more
      * metadata value than requested.
@@ -131,35 +123,29 @@ public class MetadataResource {
      * @param httpHeaders httpheaders
      * @param info        info
      * @param field       the tika metadata field name
-     * @return one of {@link javax.ws.rs.core.Response.Status#OK},
-     * {@link javax.ws.rs.core.Response.Status#NOT_FOUND}, or
-     * {@link javax.ws.rs.core.Response.Status#BAD_REQUEST}
+     * @return one of {@link javax.ws.rs.core.Response.Status#OK} or
+     * {@link javax.ws.rs.core.Response.Status#NOT_FOUND}
      * @throws Exception
      */
     @PUT
     @Path("{field}")
     @Produces({"text/csv", "application/json", "text/plain"})
     public Response getMetadataField(InputStream is, @Context HttpHeaders 
httpHeaders, @Context UriInfo info, @PathParam("field") String field) throws 
Exception {
-
-        // use BAD request to indicate that we may not have had enough data to
-        // process the request
-        Response.Status defaultErrorResponse = Response.Status.BAD_REQUEST;
         ParseContext context = tikaResource.createParseContext();
-        Metadata metadata = Metadata.newInstance(context);
-        boolean success = false;
+        Metadata metadata;
         try (TikaInputStream tis = TikaInputStream.get(is)) {
-            parseMetadata(tis, metadata, httpHeaders.getRequestHeaders(), 
info);
-            // once we've parsed the document successfully, we should use 
NOT_FOUND
-            // if we did not see the field
-            defaultErrorResponse = Response.Status.NOT_FOUND;
-            success = true;
-        } catch (Exception e) {
-            LOG.warn("Failed to process field {}", field, e);
+            tis.getPath(); // Spool to temp file for pipes-based parsing
+            metadata = parseMetadata(tis, Metadata.newInstance(context), 
httpHeaders.getRequestHeaders(), context);
+        }
+
+        String containerException = 
metadata.get(TikaCoreProperties.CONTAINER_EXCEPTION);
+        if (containerException != null && !containerException.isEmpty()) {
+            throw new TikaServerParseException(new 
TikaException(containerException));
         }
 
-        if (success == false || metadata.get(field) == null) {
+        if (metadata.get(field) == null) {
             return Response
-                    .status(defaultErrorResponse)
+                    .status(Response.Status.NOT_FOUND)
                     .entity("Failed to get metadata field " + field)
                     .build();
         }
@@ -175,21 +161,26 @@ public class MetadataResource {
                 .build();
     }
 
-    protected Metadata parseMetadata(TikaInputStream tis, Metadata metadata, 
MultivaluedMap<String, String> httpHeaders, UriInfo info)
-            throws IOException, TikaConfigException {
-        // Load default context from config (includes DigesterFactory from 
parse-context)
-        final ParseContext context = tikaResource.createParseContext();
-        Parser parser = tikaResource.createParser();
-        fillMetadata(parser, metadata, httpHeaders);
-        //no need to parse embedded docs
-        context.set(DocumentSelector.class, metadata1 -> false);
+    /**
+     * Parses via the shared pipes-backed PipesParser, stopping at the 
container document
+     * (EmbeddedLimits maxDepth=0) with content capture off ("ignore" handler) 
-- metadata
+     * only, matching /meta's contract. Set unconditionally so per-request 
config can't
+     * turn content capture back on. A container-level exception is embedded in
+     * CONTAINER_EXCEPTION here, not thrown; getMetadataField throws instead 
since it
+     * returns a bare scalar with nowhere to embed it.
+     */
+    protected Metadata parseMetadata(TikaInputStream tis, Metadata metadata, 
MultivaluedMap<String, String> httpHeaders, ParseContext context)
+            throws Exception {
+        fillMetadata(null, metadata, httpHeaders);
+        context.set(EmbeddedLimits.class, new EmbeddedLimits(0, false, 
EmbeddedLimits.UNLIMITED, false));
+        context.set(ContentHandlerFactory.class,
+                new 
BasicContentHandlerFactory(BasicContentHandlerFactory.HANDLER_TYPE.IGNORE, -1));
 
         TikaResource.logRequest(LOG, "/meta", metadata);
-        tikaResource.parse(parser, LOG, info.getPath(), tis, new 
LanguageHandler() {
-            public void endDocument() {
-                metadata.set("language", getLanguage().getLanguage());
-            }
-        }, metadata, context);
-        return metadata;
+        List<Metadata> metadataList = tikaResource.parseWithPipes(tis, 
metadata, context, ParseMode.RMETA);
+        if (metadataList.isEmpty()) {
+            return Metadata.newInstance(context);
+        }
+        return metadataList.get(0);
     }
 }
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/StackTraceTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/StackTraceTest.java
index c1aa7b7248..618c019734 100644
--- 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/StackTraceTest.java
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/StackTraceTest.java
@@ -113,7 +113,8 @@ public class StackTraceTest extends CXFTestBase {
     @Test
     public void testEncrypted() throws Exception {
         for (String path : PATHS) {
-            if ("/rmeta".equals(path)) {
+            // /rmeta and /meta embed a container exception at 200 instead of 
throwing 422.
+            if ("/rmeta".equals(path) || "/meta".equals(path)) {
                 continue;
             }
             // Use path-based routing for /tika
@@ -132,7 +133,8 @@ public class StackTraceTest extends CXFTestBase {
     @Test
     public void testNullPointerOnTika() throws Exception {
         for (String path : PATHS) {
-            if ("/rmeta".equals(path)) {
+            // Same as testEncrypted.
+            if ("/rmeta".equals(path) || "/meta".equals(path)) {
                 continue;
             }
             // Use path-based routing for /tika
@@ -170,10 +172,7 @@ public class StackTraceTest extends CXFTestBase {
     }
 
 
-    //For now, make sure that non-complete document
-    //still returns BAD_REQUEST.  We may want to
-    //make MetadataResource return the same types of parse
-    //exceptions as the others...
+    // A truncated document isn't a process failure -- NOT_FOUND, not 
BAD_REQUEST.
     @Test
     public void testMeta() throws Exception {
         InputStream stream = 
ClassLoader.getSystemResourceAsStream(TEST_HELLO_WORLD);
@@ -183,7 +182,7 @@ public class StackTraceTest extends CXFTestBase {
                 .type("application/mock+xml")
                 .accept(MediaType.TEXT_PLAIN)
                 .put(copy(stream, 100));
-        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
response.getStatus());
+        assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
response.getStatus());
         String msg = getStringFromInputStream((InputStream) 
response.getEntity());
         assertEquals("Failed to get metadata field Author", msg);
     }
diff --git 
a/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java
 
b/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java
index 3fc7b4501b..6bf07562ef 100644
--- 
a/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java
+++ 
b/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java
@@ -58,8 +58,9 @@ public class XMPMetadataResource extends MetadataResource 
implements TikaServerR
     public Response getMetadataFromMultipart(Attachment att, @Context UriInfo 
info) throws Exception {
         ParseContext context = new ParseContext();
         try (TikaInputStream tis = 
TikaInputStream.get(att.getObject(InputStream.class))) {
+            tis.getPath(); // Spool to temp file for pipes-based parsing
             return Response
-                    .ok(parseMetadata(tis, Metadata.newInstance(context), 
att.getHeaders(), info))
+                    .ok(parseMetadata(tis, Metadata.newInstance(context), 
att.getHeaders(), context))
                     .build();
         }
     }
@@ -70,8 +71,9 @@ public class XMPMetadataResource extends MetadataResource 
implements TikaServerR
         ParseContext context = new ParseContext();
         Metadata metadata = Metadata.newInstance(context);
         try (TikaInputStream tis = TikaInputStream.get(is)) {
+            tis.getPath(); // Spool to temp file for pipes-based parsing
             return Response
-                    .ok(parseMetadata(tis, metadata, 
httpHeaders.getRequestHeaders(), info))
+                    .ok(parseMetadata(tis, metadata, 
httpHeaders.getRequestHeaders(), context))
                     .build();
         }
     }
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
index 6e205486ee..e548f9d5c6 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
@@ -47,6 +47,7 @@ import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.serialization.JsonMetadata;
 import org.apache.tika.server.core.CXFTestBase;
+import org.apache.tika.server.core.TikaServerParseExceptionMapper;
 import org.apache.tika.server.core.resource.MetadataResource;
 import org.apache.tika.server.core.writer.CSVMessageBodyWriter;
 import org.apache.tika.server.core.writer.JSONMessageBodyWriter;
@@ -73,6 +74,8 @@ public class MetadataResourceTest extends CXFTestBase {
     @Override
     protected void setUpProviders(JAXRSServerFactoryBean sf) {
         List<Object> providers = new ArrayList<>();
+        // Needed by getMetadataField's TikaServerParseException throw.
+        providers.add(new TikaServerParseExceptionMapper(false));
         providers.add(new JSONMessageBodyWriter());
         providers.add(new CSVMessageBodyWriter());
         providers.add(new XMPMessageBodyWriter());
@@ -118,10 +121,13 @@ public class MetadataResourceTest extends CXFTestBase {
                 .accept("application/json")
                 .post(new MultipartBody(Arrays.asList(fileAtt)));
 
-        // Won't work, no password given - EncryptedDocumentException returns 
422
-        assertEquals(500, response.getStatus());
+        // A failed decrypt isn't a process failure -- 200, exception on the 
metadata.
+        assertEquals(200, response.getStatus());
+        Metadata noPasswordMetadata = JsonMetadata.fromJson(new 
InputStreamReader((InputStream) response.getEntity(), UTF_8));
+        assertContains("org.apache.tika.exception.EncryptedDocumentException",
+                
noPasswordMetadata.get(TikaCoreProperties.CONTAINER_EXCEPTION));
 
-        // Test 2: Wrong password - should fail
+        // Test 2: Wrong password - should fail the same way
         fileCd = new ContentDisposition("form-data; name=\"file\"; 
filename=\"test.xls\"");
         fileAtt = new Attachment("file",
                 
ClassLoader.getSystemResourceAsStream(TikaResourceTest.TEST_PASSWORD_PROTECTED),
 fileCd);
@@ -142,7 +148,10 @@ public class MetadataResourceTest extends CXFTestBase {
                 .accept("application/json")
                 .post(new MultipartBody(Arrays.asList(fileAtt, 
wrongConfigAtt)));
 
-        assertEquals(500, response.getStatus());
+        assertEquals(200, response.getStatus());
+        Metadata wrongPasswordMetadata = JsonMetadata.fromJson(new 
InputStreamReader((InputStream) response.getEntity(), UTF_8));
+        assertContains("org.apache.tika.exception.EncryptedDocumentException",
+                
wrongPasswordMetadata.get(TikaCoreProperties.CONTAINER_EXCEPTION));
 
         // Test 3: Correct password - should work
         fileCd = new ContentDisposition("form-data; name=\"file\"; 
filename=\"test.xls\"");
@@ -213,8 +222,9 @@ public class MetadataResourceTest extends CXFTestBase {
     }
 
     @Test
-    public void testGetField_Author_TEXT_Partial_BAD_REQUEST() throws 
Exception {
-
+    public void testGetField_Author_TEXT_Partial_UNPROCESSABLE() throws 
Exception {
+        // Truncating at 8000 bytes corrupts the OLE2 structure enough that 
OfficeParser
+        // throws -- a real container exception, not just a missing field.
         InputStream stream = 
ClassLoader.getSystemResourceAsStream(TikaResourceTest.TEST_DOC);
 
         Response response = WebClient
@@ -222,7 +232,7 @@ public class MetadataResourceTest extends CXFTestBase {
                 .type("application/msword")
                 .accept(MediaType.TEXT_PLAIN)
                 .put(copy(stream, 8000));
-        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
response.getStatus());
+        assertEquals(422, response.getStatus());
     }
 
     @Test

Reply via email to