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

rzo1 pushed a commit to branch OPENNLP-1890
in repository https://gitbox.apache.org/repos/asf/opennlp.git

commit 27b86da57d42d0b3d74fae0d84956aa1cdda97d9
Author: Richard Zowalla <[email protected]>
AuthorDate: Thu Jul 16 15:30:00 2026 +0200

    OPENNLP-1890: Using allowlist based class loading in StreamFactoryRegistry
---
 .../tools/cmdline/StreamFactoryRegistryTest.java   | 82 ++++++++++++++++++++++
 .../tools/cmdline/StreamFactoryRegistry.java       | 27 ++++---
 2 files changed, 94 insertions(+), 15 deletions(-)

diff --git 
a/opennlp-core/opennlp-formats/src/test/java/opennlp/tools/cmdline/StreamFactoryRegistryTest.java
 
b/opennlp-core/opennlp-formats/src/test/java/opennlp/tools/cmdline/StreamFactoryRegistryTest.java
new file mode 100644
index 000000000..839b4f6b7
--- /dev/null
+++ 
b/opennlp-core/opennlp-formats/src/test/java/opennlp/tools/cmdline/StreamFactoryRegistryTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package opennlp.tools.cmdline;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import opennlp.tools.tokenize.TokenSample;
+import opennlp.tools.util.ObjectStream;
+
+public class StreamFactoryRegistryTest {
+
+  public static class DummyFactory implements ObjectStreamFactory<String, 
Object> {
+
+    public DummyFactory() {
+    }
+
+    @Override
+    public Class<Object> getParameters() {
+      return Object.class;
+    }
+
+    @Override
+    public ObjectStream<String> create(String[] args) {
+      return null;
+    }
+  }
+
+  @Test
+  void testGetRegisteredFactory() {
+    ObjectStreamFactory<TokenSample, ?> factory =
+        StreamFactoryRegistry.getFactory(TokenSample.class, 
StreamFactoryRegistry.DEFAULT_FORMAT);
+    Assertions.assertNotNull(factory);
+  }
+
+  /**
+   * Positive: an unregistered format name referencing an opennlp.* factory 
class
+   * is loaded via the ExtensionLoader. opennlp.* is in the default allowlist 
and
+   * the type matches ObjectStreamFactory.
+   */
+  @Test
+  void testGetFactoryViaClassNameAllowedOpennlpPackage() {
+    ObjectStreamFactory<String, Object> factory =
+        StreamFactoryRegistry.getFactory(String.class, 
DummyFactory.class.getName());
+    Assertions.assertNotNull(factory);
+    Assertions.assertInstanceOf(DummyFactory.class, factory);
+  }
+
+  /**
+   * Negative: a format name referencing a non-opennlp.* class is rejected by
+   * the ExtensionLoader allowlist before Class.forName() is called.
+   */
+  @Test
+  void testGetFactoryViaClassNameBlockedPackageReturnsNull() {
+    Assertions.assertNull(StreamFactoryRegistry.getFactory(String.class, 
"com.malicious.Exploit"));
+  }
+
+  /**
+   * Negative: a format name referencing an opennlp.* class that does NOT 
implement
+   * ObjectStreamFactory is rejected by the isAssignableFrom type check.
+   */
+  @Test
+  void testGetFactoryViaClassNameWrongTypeReturnsNull() {
+    Assertions.assertNull(StreamFactoryRegistry.getFactory(String.class,
+        "opennlp.tools.tokenize.SimpleTokenizer"));
+  }
+}
diff --git 
a/opennlp-tools/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java 
b/opennlp-tools/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java
index c4bef61fb..5cb3b983b 100644
--- 
a/opennlp-tools/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java
+++ 
b/opennlp-tools/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java
@@ -17,7 +17,6 @@
 
 package opennlp.tools.cmdline;
 
-import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -70,6 +69,8 @@ import 
opennlp.tools.formats.nkjp.NKJPSentenceSampleStreamFactory;
 import opennlp.tools.formats.ontonotes.OntoNotesNameSampleStreamFactory;
 import opennlp.tools.formats.ontonotes.OntoNotesPOSSampleStreamFactory;
 import opennlp.tools.formats.ontonotes.OntoNotesParseSampleStreamFactory;
+import opennlp.tools.util.ext.ExtensionLoader;
+import opennlp.tools.util.ext.ExtensionNotLoadedException;
 
 /**
  * Registry for {@link ObjectStreamFactory object stream factories}.
@@ -203,10 +204,16 @@ public final class StreamFactoryRegistry {
   /**
    * Returns a factory which reads format named {@code formatName} and
    * instantiates streams producing objects of {@code sampleClass} class.
+   * <p>
+   * If no factory is registered for {@code formatName}, an attempt is made to
+   * interpret {@code formatName} as the fully qualified class name of an
+   * {@link ObjectStreamFactory} implementation and load it via the
+   * {@link ExtensionLoader}. Such a class must reside in an allowed package,
+   * see {@link ExtensionLoader#registerAllowedPackage(String)}.
    *
    * @param sampleClass class of the objects, produced by the streams 
instantiated by the factory
    * @param formatName  name of the format, if null, assumes OpenNLP format
-   * @return factory instance
+   * @return factory instance, or {@code null} if no factory could be found or 
loaded
    */
   @SuppressWarnings("unchecked")
   public static <T,P> ObjectStreamFactory<T,P> getFactory(Class<T> 
sampleClass, String formatName) {
@@ -222,19 +229,9 @@ public final class StreamFactoryRegistry {
     }
     else {
       try {
-        Class<?> factoryClazz = Class.forName(formatName);
-
-        // TODO: Need to check if it can produce the desired output
-        // Otherwise there will be class cast exceptions later in the flow
-
-        try {
-          return (ObjectStreamFactory<T,P>) 
factoryClazz.getDeclaredConstructor().newInstance();
-        } catch (InstantiationException | NoSuchMethodException |
-                 InvocationTargetException | IllegalAccessException e) {
-          return null;
-        }
-
-      } catch (ClassNotFoundException e) {
+        return (ObjectStreamFactory<T,P>)
+            ExtensionLoader.instantiateExtension(ObjectStreamFactory.class, 
formatName);
+      } catch (ExtensionNotLoadedException e) {
         return null;
       }
     }

Reply via email to