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

tallison 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 2972d0fcd TIKA-4581 - Round trip pojos that exist in the registry in 
the ParseContext (#2463)
2972d0fcd is described below

commit 2972d0fcd69c3d4de24fccaebc09de304efaf558
Author: Tim Allison <[email protected]>
AuthorDate: Wed Dec 17 11:08:54 2025 -0500

    TIKA-4581 - Round trip pojos that exist in the registry in the ParseContext 
(#2463)
---
 .../main/resources/META-INF/tika/other-configs.idx |   5 +
 tika-pipes/tika-pipes-core/pom.xml                 |  13 +++
 .../extractor/EmbeddedDocumentBytesConfig.java     |   3 +
 .../tika/config/loader/ComponentRegistry.java      |  36 +------
 .../tika/serialization/ComponentNameResolver.java  |  36 +++++++
 .../serialization/ParseContextDeserializer.java    |  45 ++-------
 .../tika/serialization/ParseContextSerializer.java |  50 ++++-----
 .../tika/serialization/TikaAbstractTypeMixins.java | 112 +++++++++++++++++++++
 .../TestParseContextSerialization.java             |  23 ++++-
 9 files changed, 221 insertions(+), 102 deletions(-)

diff --git 
a/tika-pipes/tika-pipes-api/src/main/resources/META-INF/tika/other-configs.idx 
b/tika-pipes/tika-pipes-api/src/main/resources/META-INF/tika/other-configs.idx
new file mode 100644
index 000000000..0c9f7d254
--- /dev/null
+++ 
b/tika-pipes/tika-pipes-api/src/main/resources/META-INF/tika/other-configs.idx
@@ -0,0 +1,5 @@
+# Component registry for tika-pipes-api
+# Format: friendly-name=fully.qualified.ClassName
+# this has to be manually generated for now because of the dependency graph
+
+handler-config=org.apache.tika.pipes.api.HandlerConfig
diff --git a/tika-pipes/tika-pipes-core/pom.xml 
b/tika-pipes/tika-pipes-core/pom.xml
index fbebcdf3a..b5c873221 100644
--- a/tika-pipes/tika-pipes-core/pom.xml
+++ b/tika-pipes/tika-pipes-core/pom.xml
@@ -98,6 +98,19 @@
           </archive>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <annotationProcessorPaths>
+            <path>
+              <groupId>org.apache.tika</groupId>
+              <artifactId>tika-annotation-processor</artifactId>
+              <version>${project.version}</version>
+            </path>
+          </annotationProcessorPaths>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 </project>
diff --git 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/EmbeddedDocumentBytesConfig.java
 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/EmbeddedDocumentBytesConfig.java
index 6a449b5bf..c02b78067 100644
--- 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/EmbeddedDocumentBytesConfig.java
+++ 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/extractor/EmbeddedDocumentBytesConfig.java
@@ -19,6 +19,9 @@ package org.apache.tika.pipes.core.extractor;
 import java.io.Serializable;
 import java.util.Objects;
 
+import org.apache.tika.config.TikaComponent;
+
+@TikaComponent(name = "embedded-document-bytes-config")
 public class EmbeddedDocumentBytesConfig implements Serializable {
 
     /**
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ComponentRegistry.java
 
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ComponentRegistry.java
index ce3593f0c..700d93761 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/config/loader/ComponentRegistry.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/config/loader/ComponentRegistry.java
@@ -43,27 +43,11 @@ import org.apache.tika.exception.TikaConfigException;
  *   <li>Optional explicit context key for ParseContext</li>
  * </ul>
  * <p>
- * Also includes built-in aliases for external dependencies that cannot be
- * annotated with @TikaComponent.
+ * Modules that can't use @TikaComponent (due to dependency constraints) can 
provide
+ * their own META-INF/tika/*.idx files to register components.
  */
 public class ComponentRegistry {
 
-    /**
-     * Built-in aliases for external dependencies.
-     * Maps component names to fully qualified class names.
-     */
-    private static final Map<String, String> BUILTIN_ALIASES = 
createBuiltinAliases();
-
-    private static Map<String, String> createBuiltinAliases() {
-        Map<String, String> aliases = new HashMap<>();
-        // HandlerConfig is in tika-pipes-api which can't depend on tika-core 
for @TikaComponent
-        aliases.put("handler-config", 
"org.apache.tika.pipes.api.HandlerConfig");
-        // EmbeddedDocumentBytesConfig is in tika-pipes-core which can't 
depend on tika-core for @TikaComponent
-        aliases.put("embedded-document-bytes-config",
-                
"org.apache.tika.pipes.core.extractor.EmbeddedDocumentBytesConfig");
-        return Collections.unmodifiableMap(aliases);
-    }
-
     private final Map<String, ComponentInfo> components;
     private final Map<Class<?>, String> classToName;  // Reverse lookup
     private final ClassLoader classLoader;
@@ -165,25 +149,9 @@ public class ComponentRegistry {
             throw new TikaConfigException("Failed to load component index: " + 
resourcePath, e);
         }
 
-        // Load built-in aliases for external dependencies
-        loadBuiltinAliases(result);
-
         return result;
     }
 
-    private void loadBuiltinAliases(Map<String, ComponentInfo> result) {
-        for (Map.Entry<String, String> alias : BUILTIN_ALIASES.entrySet()) {
-            try {
-                Class<?> clazz = Class.forName(alias.getValue(), false, 
classLoader);
-                boolean selfConfiguring = 
SelfConfiguring.class.isAssignableFrom(clazz);
-                result.put(alias.getKey(), new ComponentInfo(clazz, 
selfConfiguring, null));
-            } catch (ClassNotFoundException e) {
-                // External dependency not on classpath - skip this alias
-                // This is expected behavior, not an error
-            }
-        }
-    }
-
     private void loadFromUrl(URL url, Map<String, ComponentInfo> result) 
throws TikaConfigException {
         try (InputStream in = url.openStream();
                 BufferedReader reader = new BufferedReader(
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
index 739ed9944..0b7d9a700 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java
@@ -17,8 +17,10 @@
 package org.apache.tika.serialization;
 
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.tika.config.loader.ComponentInfo;
 import org.apache.tika.config.loader.ComponentRegistry;
 import org.apache.tika.exception.TikaConfigException;
 
@@ -85,4 +87,38 @@ public final class ComponentNameResolver {
         }
         return null;
     }
+
+    /**
+     * Checks if a component with the given name is registered in any registry.
+     *
+     * @param name the component name to check
+     * @return true if the component is registered
+     */
+    public static boolean hasComponent(String name) {
+        for (ComponentRegistry registry : REGISTRIES.values()) {
+            if (registry.hasComponent(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the component info for a given friendly name.
+     *
+     * @param name the friendly name to look up
+     * @return Optional containing the ComponentInfo, or empty if not found
+     */
+    public static Optional<ComponentInfo> getComponentInfo(String name) {
+        for (ComponentRegistry registry : REGISTRIES.values()) {
+            if (registry.hasComponent(name)) {
+                try {
+                    return Optional.of(registry.getComponentInfo(name));
+                } catch (TikaConfigException e) {
+                    // continue to next registry
+                }
+            }
+        }
+        return Optional.empty();
+    }
 }
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextDeserializer.java
 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextDeserializer.java
index d5d9fc601..d57578677 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextDeserializer.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextDeserializer.java
@@ -20,6 +20,7 @@ import static 
org.apache.tika.serialization.ParseContextSerializer.PARSE_CONTEXT
 
 import java.io.IOException;
 import java.util.Iterator;
+import java.util.Optional;
 
 import com.fasterxml.jackson.core.JacksonException;
 import com.fasterxml.jackson.core.JsonParser;
@@ -33,9 +34,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.tika.config.ConfigContainer;
 import org.apache.tika.config.SelfConfiguring;
 import org.apache.tika.config.loader.ComponentInfo;
-import org.apache.tika.config.loader.ComponentRegistry;
 import org.apache.tika.config.loader.TikaObjectMapperFactory;
-import org.apache.tika.exception.TikaConfigException;
 import org.apache.tika.parser.ParseContext;
 
 /**
@@ -57,25 +56,6 @@ public class ParseContextDeserializer extends 
JsonDeserializer<ParseContext> {
     private static final Logger LOG = 
LoggerFactory.getLogger(ParseContextDeserializer.class);
     private static final ObjectMapper MAPPER = 
TikaObjectMapperFactory.getMapper();
 
-    // Lazily loaded registry for looking up friendly names
-    private static volatile ComponentRegistry registry;
-
-    private static ComponentRegistry getRegistry() {
-        if (registry == null) {
-            synchronized (ParseContextDeserializer.class) {
-                if (registry == null) {
-                    try {
-                        registry = new ComponentRegistry("other-configs",
-                                
ParseContextDeserializer.class.getClassLoader());
-                    } catch (TikaConfigException e) {
-                        LOG.warn("Failed to load component registry for 
deserialization", e);
-                    }
-                }
-            }
-        }
-        return registry;
-    }
-
     @Override
     public ParseContext deserialize(JsonParser jsonParser,
                                     DeserializationContext 
deserializationContext)
@@ -128,22 +108,19 @@ public class ParseContextDeserializer extends 
JsonDeserializer<ParseContext> {
             }
 
             // If not found as FQCN, check registry for friendly name
+            // Use ComponentNameResolver to ensure consistency with 
TikaObjectMapperFactory's registries
             boolean isSelfConfiguring = false;
             Class<?> contextKey = null;  // The key to use when adding to 
ParseContext
             if (keyClass == null) {
-                ComponentRegistry reg = getRegistry();
-                if (reg != null && reg.hasComponent(fieldName)) {
-                    try {
-                        ComponentInfo info = reg.getComponentInfo(fieldName);
-                        keyClass = info.componentClass();
-                        isSelfConfiguring = info.selfConfiguring();
-                        contextKey = info.contextKey();
-                        LOG.debug("Resolved friendly name '{}' to class {} 
(selfConfiguring={}, contextKey={})",
-                                fieldName, keyClass.getName(), 
isSelfConfiguring,
-                                contextKey != null ? contextKey.getName() : 
"null");
-                    } catch (TikaConfigException e) {
-                        LOG.debug("Failed to get component info for '{}': {}", 
fieldName, e.getMessage());
-                    }
+                Optional<ComponentInfo> infoOpt = 
ComponentNameResolver.getComponentInfo(fieldName);
+                if (infoOpt.isPresent()) {
+                    ComponentInfo info = infoOpt.get();
+                    keyClass = info.componentClass();
+                    isSelfConfiguring = info.selfConfiguring();
+                    contextKey = info.contextKey();
+                    LOG.debug("Resolved friendly name '{}' to class {} 
(selfConfiguring={}, contextKey={})",
+                            fieldName, keyClass.getName(), isSelfConfiguring,
+                            contextKey != null ? contextKey.getName() : 
"null");
                 }
             } else {
                 // For FQCN resolution, check SelfConfiguring directly
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextSerializer.java
 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextSerializer.java
index bca2ef54a..e07559fab 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextSerializer.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/serialization/ParseContextSerializer.java
@@ -29,9 +29,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.tika.config.ConfigContainer;
-import org.apache.tika.config.loader.ComponentRegistry;
 import org.apache.tika.config.loader.TikaObjectMapperFactory;
-import org.apache.tika.exception.TikaConfigException;
 import org.apache.tika.parser.ParseContext;
 
 /**
@@ -57,27 +55,12 @@ public class ParseContextSerializer extends 
JsonSerializer<ParseContext> {
     private static final Logger LOG = 
LoggerFactory.getLogger(ParseContextSerializer.class);
     public static final String PARSE_CONTEXT = "parseContext";
 
+    // Full mapper with polymorphic type handling (includes 
WrapperObjectSerializer)
     private static final ObjectMapper MAPPER = 
TikaObjectMapperFactory.getMapper();
 
-    // Lazily loaded registry for looking up friendly names
-    private static volatile ComponentRegistry registry;
-
-    private static ComponentRegistry getRegistry() {
-        if (registry == null) {
-            synchronized (ParseContextSerializer.class) {
-                if (registry == null) {
-                    try {
-                        registry = new ComponentRegistry("other-configs",
-                                ParseContextSerializer.class.getClassLoader());
-                    } catch (TikaConfigException e) {
-                        LOG.warn("Failed to load component registry for 
serialization", e);
-                        // Return null - objects without friendly names won't 
be serialized
-                    }
-                }
-            }
-        }
-        return registry;
-    }
+    // Plain mapper without WrapperObjectSerializer - for types with friendly 
names
+    // where the wrapper is added at the field name level by this serializer
+    private static final ObjectMapper PLAIN_MAPPER = new ObjectMapper();
 
     @Override
     public void serialize(ParseContext parseContext, JsonGenerator 
jsonGenerator,
@@ -98,7 +81,6 @@ public class ParseContextSerializer extends 
JsonSerializer<ParseContext> {
 
         // Then, serialize objects from ParseContext that have registered 
friendly names
         // or are stored under Tika type keys (for polymorphic custom 
subclasses)
-        ComponentRegistry reg = getRegistry();
         Map<String, Object> contextMap = parseContext.getContextMap();
         for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
             // Skip ConfigContainer - already handled above
@@ -112,7 +94,8 @@ public class ParseContextSerializer extends 
JsonSerializer<ParseContext> {
             }
 
             // Try to get friendly name for this object's class
-            String friendlyName = (reg != null) ? 
reg.getFriendlyName(value.getClass()) : null;
+            // Use ComponentNameResolver to ensure consistency with 
TikaObjectMapperFactory's registries
+            String friendlyName = 
ComponentNameResolver.getFriendlyName(value.getClass());
 
             // Determine key: prefer friendly name, fall back to FQCN for Tika 
types
             String key;
@@ -129,17 +112,18 @@ public class ParseContextSerializer extends 
JsonSerializer<ParseContext> {
 
             if (!writtenKeys.contains(key)) {
                 jsonGenerator.writeFieldName(key);
-                // Write wrapper object format with type info for polymorphic 
deserialization
-                // Format: {"concrete-class-name": {properties...}}
-                jsonGenerator.writeStartObject();
-                String typeName = (friendlyName != null) ? friendlyName :
-                        
ComponentNameResolver.getFriendlyName(value.getClass());
-                if (typeName == null) {
-                    typeName = value.getClass().getName();
+                if (friendlyName != null) {
+                    // Type has friendly name - use plain mapper to write 
properties directly
+                    // (key already serves as the type identifier)
+                    PLAIN_MAPPER.writeValue(jsonGenerator, value);
+                } else {
+                    // No friendly name - add wrapper with FQCN and use MAPPER 
for
+                    // polymorphic type handling of nested types
+                    jsonGenerator.writeStartObject();
+                    jsonGenerator.writeFieldName(value.getClass().getName());
+                    MAPPER.writeValue(jsonGenerator, value);
+                    jsonGenerator.writeEndObject();
                 }
-                jsonGenerator.writeFieldName(typeName);
-                MAPPER.writeValue(jsonGenerator, value);
-                jsonGenerator.writeEndObject();
                 writtenKeys.add(key);
             }
         }
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/serialization/TikaAbstractTypeMixins.java
 
b/tika-serialization/src/main/java/org/apache/tika/serialization/TikaAbstractTypeMixins.java
index 7c68042aa..2a11b0e76 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/serialization/TikaAbstractTypeMixins.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/serialization/TikaAbstractTypeMixins.java
@@ -19,6 +19,7 @@ package org.apache.tika.serialization;
 import java.io.IOException;
 import java.lang.reflect.Modifier;
 
+import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.BeanDescription;
 import com.fasterxml.jackson.databind.DeserializationConfig;
@@ -26,9 +27,13 @@ import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
 import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,12 +69,15 @@ public final class TikaAbstractTypeMixins {
 
     /**
      * Registers the abstract type handling module on the given ObjectMapper.
+     * This includes both serializers (to add type wrappers) and deserializers
+     * (to resolve type wrappers).
      *
      * @param mapper the ObjectMapper to configure
      */
     public static void registerDeserializers(ObjectMapper mapper) {
         SimpleModule module = new SimpleModule("TikaAbstractTypes");
         module.setDeserializerModifier(new 
AbstractTypeDeserializerModifier(mapper));
+        module.setSerializerModifier(new 
AbstractTypeSerializerModifier(mapper));
         mapper.registerModule(module);
     }
 
@@ -198,4 +206,108 @@ public final class TikaAbstractTypeMixins {
             }
         }
     }
+
+    /**
+     * Modifier that intercepts serialization of values declared as abstract 
types
+     * and wraps them with type information.
+     */
+    private static class AbstractTypeSerializerModifier extends 
BeanSerializerModifier {
+
+        private final ObjectMapper mapper;
+
+        AbstractTypeSerializerModifier(ObjectMapper mapper) {
+            this.mapper = mapper;
+        }
+
+        @Override
+        public JsonSerializer<?> modifySerializer(SerializationConfig config,
+                                                   BeanDescription beanDesc,
+                                                   JsonSerializer<?> 
serializer) {
+            Class<?> beanClass = beanDesc.getBeanClass();
+
+            // Skip types that shouldn't use wrapper format
+            if (shouldSkip(beanClass)) {
+                return serializer;
+            }
+
+            // For concrete Tika types, wrap with type name if they 
extend/implement an abstract type
+            // This ensures polymorphic types in lists get properly wrapped
+            if (isTikaPolymorphicType(beanClass)) {
+                LOG.debug("Registering wrapper serializer for polymorphic 
type: {}",
+                        beanClass.getName());
+                return new WrapperObjectSerializer<>(serializer, mapper);
+            }
+
+            return serializer;
+        }
+
+        private boolean shouldSkip(Class<?> beanClass) {
+            // Skip primitives and their wrappers
+            if (beanClass.isPrimitive()) {
+                return true;
+            }
+
+            // Skip common JDK types
+            String name = beanClass.getName();
+            if (name.startsWith("java.") || name.startsWith("javax.")) {
+                return true;
+            }
+
+            // Skip arrays
+            if (beanClass.isArray()) {
+                return true;
+            }
+
+            // Skip abstract types (we want to wrap concrete implementations, 
not the abstract types themselves)
+            if (beanClass.isInterface() || 
Modifier.isAbstract(beanClass.getModifiers())) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks if this class should be wrapped with type information during 
serialization.
+         * Only types registered in the component registry are wrapped - this 
excludes
+         * container types (like CompositeMetadataFilter) that are not in the 
registry.
+         */
+        private boolean isTikaPolymorphicType(Class<?> beanClass) {
+            // Only wrap types that have a registered friendly name in the 
registry
+            return ComponentNameResolver.getFriendlyName(beanClass) != null;
+        }
+    }
+
+    /**
+     * Serializer that wraps objects with their type name.
+     * Output format: {"type-name": {...properties...}}
+     */
+    private static class WrapperObjectSerializer<T> extends JsonSerializer<T> {
+
+        private final JsonSerializer<T> delegate;
+        private final ObjectMapper mapper;
+
+        @SuppressWarnings("unchecked")
+        WrapperObjectSerializer(JsonSerializer<?> delegate, ObjectMapper 
mapper) {
+            this.delegate = (JsonSerializer<T>) delegate;
+            this.mapper = mapper;
+        }
+
+        @Override
+        public void serialize(T value, JsonGenerator gen, SerializerProvider 
serializers)
+                throws IOException {
+            if (value == null) {
+                gen.writeNull();
+                return;
+            }
+
+            // Get the friendly name (guaranteed to exist since we only wrap 
registered types)
+            String typeName = 
ComponentNameResolver.getFriendlyName(value.getClass());
+
+            // Write wrapper: {"type-name": {...}}
+            gen.writeStartObject();
+            gen.writeFieldName(typeName);
+            delegate.serialize(value, gen, serializers);
+            gen.writeEndObject();
+        }
+    }
 }
diff --git 
a/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
 
b/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
index 3b06f4079..5292ece26 100644
--- 
a/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
+++ 
b/tika-serialization/src/test/java/org/apache/tika/serialization/TestParseContextSerialization.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.StringWriter;
 import java.io.Writer;
+import java.util.List;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -38,6 +39,7 @@ import org.apache.tika.extractor.SkipEmbeddedDocumentSelector;
 import org.apache.tika.metadata.filter.AttachmentCountingListFilter;
 import org.apache.tika.metadata.filter.CompositeMetadataFilter;
 import org.apache.tika.metadata.filter.MetadataFilter;
+import org.apache.tika.metadata.filter.MockUpperCaseFilter;
 import org.apache.tika.parser.ParseContext;
 
 /**
@@ -300,7 +302,7 @@ public class TestParseContextSerialization {
     }
 
     @Test
-    public void testMetadataList() throws Exception {
+    public void testMetadataListConfigContainer() throws Exception {
         ConfigContainer configContainer = new ConfigContainer();
         configContainer.set("metadata-filters", """
             [
@@ -322,6 +324,25 @@ public class TestParseContextSerialization {
         assertEquals(AttachmentCountingListFilter.class, 
deserFilter.getFilters().get(0).getClass());
     }
 
+
+    @Test
+    public void testMetadataListPOJO() throws Exception {
+        CompositeMetadataFilter metadataFilter = new 
CompositeMetadataFilter(List.of(new AttachmentCountingListFilter(), new 
MockUpperCaseFilter()));
+
+        ParseContext parseContext = new ParseContext();
+        parseContext.set(MetadataFilter.class, metadataFilter);
+
+        ObjectMapper mapper = createMapper();
+        String json = mapper.writeValueAsString(parseContext);
+
+        ParseContext deser = mapper.readValue(json, ParseContext.class);
+        MetadataFilter resolvedFilter = deser.get(MetadataFilter.class);
+        assertNotNull(resolvedFilter, "MetadataFilter should be resolved");
+        assertEquals(CompositeMetadataFilter.class, resolvedFilter.getClass());
+        CompositeMetadataFilter deserFilter = (CompositeMetadataFilter) 
resolvedFilter;
+        assertEquals(AttachmentCountingListFilter.class, 
deserFilter.getFilters().get(0).getClass());
+    }
+
     @Test
     public void testContextKeyDeserialization() throws Exception {
         // Test that components with @TikaComponent(contextKey=...) are stored

Reply via email to