This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/opennlp.git
commit 4a435d3c39b2cd0d78c4ece35343d364f2de438b 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/StreamFactoryRegistry.java | 27 ++++--- .../tools/cmdline/StreamFactoryRegistryTest.java | 82 ++++++++++++++++++++++ 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/opennlp-core/opennlp-formats/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java b/opennlp-core/opennlp-formats/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java index 09cfef02a..14972980b 100644 --- a/opennlp-core/opennlp-formats/src/main/java/opennlp/tools/cmdline/StreamFactoryRegistry.java +++ b/opennlp-core/opennlp-formats/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; @@ -72,6 +71,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}. @@ -208,10 +209,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) { @@ -227,19 +234,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; } } 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")); + } +}
