You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opennlp.apache.org by rz...@apache.org on 2026/07/16 14:15:36 UTC
(opennlp) 02/03: OPENNLP-1890: Using allowlist based class loading in StreamFactoryRegistry
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 <rz...@apache.org>
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;
}
}