diff --git a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
index 3e767baa81..e39b75d949 100644
--- a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
+++ b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
@@ -88,7 +88,7 @@ public final class PropertyDescriptor implements Comparable
private final ExpressionLanguageScope expressionLanguageScope;
/**
* indicates whether or not this property represents resources that should be added
- * to the classpath for this instance of the component
+ * to the classpath and used for loading native libraries for this instance of the component
*/
private final boolean dynamicallyModifiesClasspath;
@@ -310,11 +310,21 @@ public final class PropertyDescriptor implements Comparable
/**
* Specifies that the value of this property represents one or more resources that the
- * framework should add to the classpath of the given component.
- *
+ * framework should add to the classpath of as well as consider when looking for native
+ * libraries for the given component.
+ *
* NOTE: If a component contains a PropertyDescriptor where dynamicallyModifiesClasspath is set to true,
- * the component must also be annotated with @RequiresInstanceClassloading, otherwise the component will be
- * considered invalid.
+ * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will
+ * be loaded by a separate InstanceClassLoader for each processor instance.
+ * It also allows to load native libraries from the extra classpath.
+ *
+ * One can chose to omit the annotation. In this case the loading of native libraries from the extra classpath
+ * is not supported.
+ * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an
+ * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually
+ * to load required classes on an instance-by-instance basis
+ * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example).
+ *
*
* @param dynamicallyModifiesClasspath whether or not this property should be used by the framework to modify the classpath
* @return the builder
diff --git a/nifi-docs/src/main/asciidoc/developer-guide.adoc b/nifi-docs/src/main/asciidoc/developer-guide.adoc
index bd79c1ae63..c0c98df5a0 100644
--- a/nifi-docs/src/main/asciidoc/developer-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/developer-guide.adoc
@@ -2479,6 +2479,9 @@ attempts to resolve filesystem resources from the value of the property. The val
comma-separated list of one or more directories or files, where any paths that do not exist are
skipped. If the resource represents a directory, the directory is listed, and all of the files
in that directory are added to the classpath individually.
+These directories also will be scanned for native libraries. If a library is found in one of these
+directories, an OS-handled temporary copy is created and cached before loading it to maintain consistency
+and classloader isolation.
Each property may impose further restrictions on the format of the value through the validators.
For example, using StandardValidators.FILE_EXISTS_VALIDATOR restricts the property to accepting a
diff --git a/nifi-nar-bundles/nifi-extension-utils/nifi-hadoop-utils/src/main/java/org/apache/nifi/processors/hadoop/AbstractHadoopProcessor.java b/nifi-nar-bundles/nifi-extension-utils/nifi-hadoop-utils/src/main/java/org/apache/nifi/processors/hadoop/AbstractHadoopProcessor.java
index e742d13e3c..a27b10408e 100644
--- a/nifi-nar-bundles/nifi-extension-utils/nifi-hadoop-utils/src/main/java/org/apache/nifi/processors/hadoop/AbstractHadoopProcessor.java
+++ b/nifi-nar-bundles/nifi-extension-utils/nifi-hadoop-utils/src/main/java/org/apache/nifi/processors/hadoop/AbstractHadoopProcessor.java
@@ -112,8 +112,8 @@ public abstract class AbstractHadoopProcessor extends AbstractProcessor {
public static final PropertyDescriptor ADDITIONAL_CLASSPATH_RESOURCES = new PropertyDescriptor.Builder()
.name("Additional Classpath Resources")
- .description("A comma-separated list of paths to files and/or directories that will be added to the classpath. When specifying a " +
- "directory, all files with in the directory will be added to the classpath, but further sub-directories will not be included.")
+ .description("A comma-separated list of paths to files and/or directories that will be added to the classpath and used for loading native libraries. " +
+ "When specifying a directory, all files with in the directory will be added to the classpath, but further sub-directories will not be included.")
.required(false)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.dynamicallyModifiesClasspath(true)
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
index c9b7ff164d..bfe3d37621 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
@@ -22,6 +22,10 @@ language governing permissions and limitations under the License. -->
org.apache.nifi
nifi-framework-nar-utils
+
+ org.apache.nifi
+ nifi-nar-utils
+
org.apache.nifi
nifi-utils
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
index 687d347f10..65a2471420 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
@@ -17,6 +17,7 @@
package org.apache.nifi.controller;
+import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
@@ -27,7 +28,6 @@ import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.components.validation.ValidationTrigger;
import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.junit.Test;
@@ -88,7 +88,7 @@ public class TestAbstractComponentNode {
public ValidationControlledAbstractComponentNode(final long pauseMillis, final ValidationTrigger validationTrigger) {
super("id", Mockito.mock(ValidationContextFactory.class), Mockito.mock(ControllerServiceProvider.class), "unit test component",
ValidationControlledAbstractComponentNode.class.getCanonicalName(), Mockito.mock(ComponentVariableRegistry.class), Mockito.mock(ReloadComponent.class),
- Mockito.mock(StandardExtensionDiscoveringManager.class), validationTrigger, false);
+ Mockito.mock(ExtensionManager.class), validationTrigger, false);
this.pauseMillis = pauseMillis;
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
index d1fb511587..62e1288fea 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
@@ -17,9 +17,11 @@
package org.apache.nifi.controller;
+import org.apache.nifi.admin.service.AuditService;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
+import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.PropertyDescriptor;
@@ -29,11 +31,16 @@ import org.apache.nifi.controller.exception.ControllerServiceInstantiationExcept
import org.apache.nifi.controller.exception.ProcessorInstantiationException;
import org.apache.nifi.controller.kerberos.KerberosConfig;
import org.apache.nifi.controller.reporting.ReportingTaskInstantiationException;
+import org.apache.nifi.controller.repository.FlowFileEventRepository;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.engine.FlowEngine;
+import org.apache.nifi.events.VolatileBulletinRepository;
import org.apache.nifi.expression.ExpressionLanguageCompiler;
import org.apache.nifi.nar.ExtensionDiscoveringManager;
+import org.apache.nifi.nar.InstanceClassLoader;
+import org.apache.nifi.nar.NarClassLoader;
import org.apache.nifi.nar.NarCloseable;
+import org.apache.nifi.nar.OSUtil;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.parameter.ParameterContext;
@@ -46,8 +53,11 @@ import org.apache.nifi.processor.StandardProcessContext;
import org.apache.nifi.processor.StandardProcessorInitializationContext;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.provenance.MockProvenanceRepository;
import org.apache.nifi.registry.VariableDescriptor;
import org.apache.nifi.registry.VariableRegistry;
+import org.apache.nifi.registry.flow.FlowRegistryClient;
+import org.apache.nifi.registry.variable.FileBasedVariableRegistry;
import org.apache.nifi.registry.variable.StandardComponentVariableRegistry;
import org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor;
import org.apache.nifi.test.processors.ModifiesClasspathProcessor;
@@ -58,7 +68,6 @@ import org.apache.nifi.util.SynchronousValidationTrigger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
import java.io.File;
import java.net.MalformedURLException;
@@ -75,11 +84,17 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicReference;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class TestStandardProcessorNode {
@@ -88,13 +103,25 @@ public class TestStandardProcessorNode {
private ExtensionDiscoveringManager extensionManager;
private NiFiProperties niFiProperties;
+ private final AtomicReference currentInstanceClassLoaderHolder = new AtomicReference<>();
+
@Before
public void setup() {
variableRegistry = new MockVariableRegistry();
niFiProperties = NiFiProperties.createBasicNiFiProperties("src/test/resources/conf/nifi.properties", null);
systemBundle = SystemBundle.create(niFiProperties);
- extensionManager = new StandardExtensionDiscoveringManager();
+ extensionManager = new StandardExtensionDiscoveringManager() {
+ @Override
+ public InstanceClassLoader createInstanceClassLoader(String classType, String instanceIdentifier, Bundle bundle, Set additionalUrls) {
+ InstanceClassLoader instanceClassLoader = super.createInstanceClassLoader(classType, instanceIdentifier, bundle, additionalUrls);
+
+ currentInstanceClassLoaderHolder.set(instanceClassLoader);
+
+ return instanceClassLoader;
+
+ }
+ };
extensionManager.discoverExtensions(systemBundle, Collections.emptySet());
}
@@ -106,8 +133,8 @@ public class TestStandardProcessorNode {
ProcessorInitializationContext initContext = new StandardProcessorInitializationContext(uuid, null, null, null, KerberosConfig.NOT_CONFIGURED);
processor.initialize(initContext);
- final ReloadComponent reloadComponent = Mockito.mock(ReloadComponent.class);
- final BundleCoordinate coordinate = Mockito.mock(BundleCoordinate.class);
+ final ReloadComponent reloadComponent = mock(ReloadComponent.class);
+ final BundleCoordinate coordinate = mock(BundleCoordinate.class);
final LoggableComponent loggableComponent = new LoggableComponent<>(processor, coordinate, null);
final StandardProcessorNode procNode = new StandardProcessorNode(loggableComponent, uuid, createValidationContextFactory(), null, null,
@@ -179,6 +206,69 @@ public class TestStandardProcessorNode {
}
}
+ @Test
+ public void testNativeLibLoadedFromDynamicallyModifiesClasspathProperty() throws Exception {
+ // GIVEN
+ assumeTrue("Test only runs on Mac OS", new OSUtil(){}.isOsMac());
+
+ // Init NiFi
+ NarClassLoader narClassLoader = mock(NarClassLoader.class);
+ when(narClassLoader.getURLs()).thenReturn(new URL[0]);
+
+ Bundle narBundle = SystemBundle.create(niFiProperties, narClassLoader);
+
+ HashMap additionalProperties = new HashMap<>();
+ additionalProperties.put(NiFiProperties.ADMINISTRATIVE_YIELD_DURATION, "1 sec");
+ additionalProperties.put(NiFiProperties.STATE_MANAGEMENT_CONFIG_FILE, "target/test-classes/state-management.xml");
+ additionalProperties.put(NiFiProperties.STATE_MANAGEMENT_LOCAL_PROVIDER_ID, "local-provider");
+ additionalProperties.put(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, MockProvenanceRepository.class.getName());
+ additionalProperties.put("nifi.remote.input.socket.port", "");
+ additionalProperties.put("nifi.remote.input.secure", "");
+
+ final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties("src/test/resources/conf/nifi.properties", additionalProperties);
+
+ final FlowController flowController = FlowController.createStandaloneInstance(mock(FlowFileEventRepository.class), nifiProperties,
+ mock(Authorizer.class), mock(AuditService.class), null, new VolatileBulletinRepository(),
+ new FileBasedVariableRegistry(nifiProperties.getVariableRegistryPropertiesPaths()),
+ mock(FlowRegistryClient.class), extensionManager);
+
+ // Init processor
+ final PropertyDescriptor classpathProp = new PropertyDescriptor.Builder().name("Classpath Resources")
+ .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+
+ final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp));
+ final String uuid = UUID.randomUUID().toString();
+
+ final ValidationContextFactory validationContextFactory = createValidationContextFactory();
+ final ProcessScheduler processScheduler = mock(ProcessScheduler.class);
+ final TerminationAwareLogger componentLog = mock(TerminationAwareLogger.class);
+
+ final ReloadComponent reloadComponent = new StandardReloadComponent(flowController);
+ ProcessorInitializationContext initContext = new StandardProcessorInitializationContext(uuid, componentLog, null, null, KerberosConfig.NOT_CONFIGURED);
+ ((Processor) processor).initialize(initContext);
+
+ final LoggableComponent loggableComponent = new LoggableComponent<>(processor, narBundle.getBundleDetails().getCoordinate(), componentLog);
+ final StandardProcessorNode procNode = new StandardProcessorNode(loggableComponent, uuid, validationContextFactory, processScheduler,
+ null, new StandardComponentVariableRegistry(variableRegistry), reloadComponent, extensionManager, new SynchronousValidationTrigger());
+
+ final Map properties = new HashMap<>();
+ properties.put(classpathProp.getName(), "src/test/resources/native");
+ procNode.setProperties(properties);
+
+ try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, procNode.getProcessor().getClass(), procNode.getIdentifier())){
+ // Should pass validation
+ assertTrue(procNode.computeValidationErrors(procNode.getValidationContext()).isEmpty());
+
+ // WHEN
+ String actualLibraryLocation = currentInstanceClassLoaderHolder.get().findLibrary("testjni");
+
+ // THEN
+ assertThat(actualLibraryLocation, containsString(currentInstanceClassLoaderHolder.get().getIdentifier()));
+ } finally {
+ extensionManager.removeInstanceClassLoader(procNode.getIdentifier());
+ }
+ }
+
@Test
public void testUpdateOtherPropertyDoesNotImpactClasspath() throws MalformedURLException {
@@ -400,8 +490,8 @@ public class TestStandardProcessorNode {
private StandardProcessorNode createProcessorNode(final Processor processor, final ReloadComponent reloadComponent) {
final String uuid = UUID.randomUUID().toString();
final ValidationContextFactory validationContextFactory = createValidationContextFactory();
- final ProcessScheduler processScheduler = Mockito.mock(ProcessScheduler.class);
- final TerminationAwareLogger componentLog = Mockito.mock(TerminationAwareLogger.class);
+ final ProcessScheduler processScheduler = mock(ProcessScheduler.class);
+ final TerminationAwareLogger componentLog = mock(TerminationAwareLogger.class);
extensionManager.createInstanceClassLoader(processor.getClass().getName(), uuid, systemBundle, null);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/native/libtestjni.dylib b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/native/libtestjni.dylib
new file mode 100644
index 0000000000..a06ff7c74f
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/native/libtestjni.dylib differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/AbstractTestNarLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/AbstractTestNarLoader.java
new file mode 100644
index 0000000000..70daa63172
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/AbstractTestNarLoader.java
@@ -0,0 +1,114 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.util.NiFiProperties;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public abstract class AbstractTestNarLoader {
+ abstract String getWorkDir();
+ abstract String getNarAutoloadDir();
+ abstract String getPropertiesFile();
+
+ Bundle systemBundle;
+ NiFiProperties properties;
+ ExtensionMapping extensionMapping;
+
+ StandardNarLoader narLoader;
+ NarClassLoaders narClassLoaders;
+ ExtensionDiscoveringManager extensionManager;
+
+ @Before
+ public void setup() throws IOException, ClassNotFoundException {
+ deleteDir(getWorkDir());
+ deleteDir(getNarAutoloadDir());
+
+ final File extensionsDir = new File(getNarAutoloadDir());
+ assertTrue(extensionsDir.mkdirs());
+
+ // Create NiFiProperties
+ final String propertiesFile = getPropertiesFile();
+ properties = NiFiProperties.createBasicNiFiProperties(propertiesFile, Collections.emptyMap());
+
+ // Unpack NARs
+ systemBundle = SystemBundle.create(properties);
+ extensionMapping = NarUnpacker.unpackNars(properties, systemBundle);
+ assertEquals(0, extensionMapping.getAllExtensionNames().size());
+
+ // Initialize NarClassLoaders
+ narClassLoaders = new NarClassLoaders();
+ narClassLoaders.init(properties.getFrameworkWorkingDirectory(), properties.getExtensionsWorkingDirectory());
+
+ extensionManager = new StandardExtensionDiscoveringManager();
+
+ // Should have Framework and Jetty NARs loaded here
+ assertEquals(2, narClassLoaders.getBundles().size());
+
+ // No extensions should be loaded yet
+ assertEquals(0, extensionManager.getExtensions(Processor.class).size());
+ assertEquals(0, extensionManager.getExtensions(ControllerService.class).size());
+ assertEquals(0, extensionManager.getExtensions(ReportingTask.class).size());
+
+ // Create class we are testing
+ narLoader = new StandardNarLoader(
+ properties.getExtensionsWorkingDirectory(),
+ properties.getComponentDocumentationWorkingDirectory(),
+ narClassLoaders,
+ extensionManager,
+ extensionMapping,
+ (bundles) -> {
+ });
+ }
+
+ private void deleteDir(String path) throws IOException {
+ Path directory = Paths.get(path);
+ if (!directory.toFile().exists()) {
+ return;
+ }
+
+ Files.walkFileTree(directory, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibFromNar.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibFromNar.java
new file mode 100644
index 0000000000..575a66e356
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibFromNar.java
@@ -0,0 +1,145 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.Bundle;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+public class TestLoadNativeLibFromNar extends AbstractTestNarLoader {
+ static final String WORK_DIR = "./target/work";
+ static final String NAR_AUTOLOAD_DIR = "./target/nars_with_native_lib";
+ static final String PROPERTIES_FILE = "./src/test/resources/conf/nifi.nar_with_native_lib.properties";
+ static final String EXTENSIONS_DIR = "./src/test/resources/nars_with_native_lib";
+
+ @BeforeClass
+ public static void setUpSuite() {
+ assumeTrue("Test only runs on Mac OS", new OSUtil(){}.isOsMac());
+ }
+
+ @Test
+ public void testLoadSameLibraryFromBy2NarClassLoadersFromNar() throws Exception {
+ final File extensionsDir = new File(EXTENSIONS_DIR);
+ final Path narAutoLoadDir = Paths.get(NAR_AUTOLOAD_DIR);
+ for (final File extensionFile : extensionsDir.listFiles()) {
+ Files.copy(extensionFile.toPath(), narAutoLoadDir.resolve(extensionFile.getName()), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ final List narFiles = Arrays.asList(narAutoLoadDir.toFile().listFiles());
+ assertEquals(2, narFiles.size());
+
+ final NarLoadResult narLoadResult = narLoader.load(narFiles);
+ assertNotNull(narLoadResult);
+
+ List narClassLoaders = this.narClassLoaders.getBundles().stream()
+ .filter(bundle -> bundle.getBundleDetails().getCoordinate().getCoordinate().contains("nifi-nar_with_native_lib-"))
+ .map(Bundle::getClassLoader)
+ .filter(NarClassLoader.class::isInstance)
+ .map(NarClassLoader.class::cast)
+ .collect(Collectors.toList());
+
+ Set actualLibraryLocations = narClassLoaders.stream()
+ .map(classLoader -> classLoader.findLibrary("testjni"))
+ .collect(Collectors.toSet());
+
+ for (NarClassLoader narClassLoader : narClassLoaders) {
+ Class> TestJNI = narClassLoader.loadClass("org.apache.nifi.nar.sharedlib.TestJNI");
+
+ Object actualJniMethodReturnValue = TestJNI
+ .getMethod("testJniMethod")
+ .invoke(TestJNI.newInstance());
+
+ assertEquals("calledNativeTestJniMethod", actualJniMethodReturnValue);
+ }
+
+ assertEquals(2, actualLibraryLocations.size());
+ assertThat(actualLibraryLocations, hasItem(containsString("nifi-nar_with_native_lib-1")));
+ assertThat(actualLibraryLocations, hasItem(containsString("nifi-nar_with_native_lib-2")));
+ }
+
+ @Test
+ public void testLoadSameLibraryBy2InstanceClassLoadersFromNar() throws Exception {
+ final File extensionsDir = new File(EXTENSIONS_DIR);
+ final Path narAutoLoadDir = Paths.get(NAR_AUTOLOAD_DIR);
+ for (final File extensionFile : extensionsDir.listFiles()) {
+ Files.copy(extensionFile.toPath(), narAutoLoadDir.resolve(extensionFile.getName()), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ final List narFiles = Arrays.asList(narAutoLoadDir.toFile().listFiles());
+ assertEquals(2, narFiles.size());
+
+ final NarLoadResult narLoadResult = narLoader.load(narFiles);
+ assertNotNull(narLoadResult);
+
+ Bundle bundleWithNativeLib = this.narClassLoaders.getBundles().stream()
+ .filter(bundle -> bundle.getBundleDetails().getCoordinate().getCoordinate().contains("nifi-nar_with_native_lib-"))
+ .findFirst().get();
+
+ Class> processorClass = bundleWithNativeLib.getClassLoader().loadClass("org.apache.nifi.nar.ModifiesClasspathProcessor");
+
+ List instanceClassLoaders = Arrays.asList(
+ extensionManager.createInstanceClassLoader(processorClass.getName(), UUID.randomUUID().toString(), bundleWithNativeLib, null),
+ extensionManager.createInstanceClassLoader(processorClass.getName(), UUID.randomUUID().toString(), bundleWithNativeLib, null)
+ );
+
+ for (InstanceClassLoader instanceClassLoader : instanceClassLoaders) {
+ String actualLibraryLocation = instanceClassLoader.findLibrary("testjni");
+
+ Class> TestJNI = instanceClassLoader.loadClass("org.apache.nifi.nar.sharedlib.TestJNI");
+
+ Object actualJniMethodReturnValue = TestJNI
+ .getMethod("testJniMethod")
+ .invoke(TestJNI.newInstance());
+
+ assertThat(actualLibraryLocation, containsString(instanceClassLoader.getIdentifier()));
+ assertEquals("calledNativeTestJniMethod", actualJniMethodReturnValue);
+ }
+ }
+
+ @Override
+ String getWorkDir() {
+ return WORK_DIR;
+ }
+
+ @Override
+ String getNarAutoloadDir() {
+ return NAR_AUTOLOAD_DIR;
+ }
+
+ @Override
+ String getPropertiesFile() {
+ return PROPERTIES_FILE;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibViaSystemProperty.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibViaSystemProperty.java
new file mode 100644
index 0000000000..cbb569c593
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibViaSystemProperty.java
@@ -0,0 +1,160 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.Bundle;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+public class TestLoadNativeLibViaSystemProperty extends AbstractTestNarLoader {
+ static final String WORK_DIR = "./target/work";
+ static final String NAR_AUTOLOAD_DIR = "./target/nars_without_native_lib";
+ static final String PROPERTIES_FILE = "./src/test/resources/conf/nifi.nar_without_native_lib.properties";
+ static final String EXTENSIONS_DIR = "./src/test/resources/nars_without_native_lib";
+
+ private static String oldJavaLibraryPath;
+
+ @BeforeClass
+ public static void setUpClass() {
+ assumeTrue("Test only runs on Mac OS", new OSUtil(){}.isOsMac());
+
+ oldJavaLibraryPath = System.getProperty("java.library.path");
+ System.setProperty("java.library.path", "./src/test/resources/native");
+ }
+
+ @AfterClass
+ public static void tearDownSuite() {
+ if (oldJavaLibraryPath != null) {
+ System.setProperty("java.library.path", oldJavaLibraryPath);
+ oldJavaLibraryPath = null;
+ }
+ }
+
+ @Test
+ public void testLoadSameLibraryByNarClassLoaderFromSystemProperty() throws Exception {
+ final File extensionsDir = new File(EXTENSIONS_DIR);
+ final Path narAutoLoadDir = Paths.get(NAR_AUTOLOAD_DIR);
+ for (final File extensionFile : extensionsDir.listFiles()) {
+ Files.copy(extensionFile.toPath(), narAutoLoadDir.resolve(extensionFile.getName()), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ final List narFiles = Arrays.asList(narAutoLoadDir.toFile().listFiles());
+ assertEquals(1, narFiles.size());
+
+ final NarLoadResult narLoadResult = narLoader.load(narFiles);
+ assertNotNull(narLoadResult);
+
+ List narClassLoaders = this.narClassLoaders.getBundles().stream()
+ .filter(bundle -> bundle.getBundleDetails().getCoordinate().getCoordinate().contains("nifi-nar_without_native_lib-"))
+ .map(Bundle::getClassLoader)
+ .filter(NarClassLoader.class::isInstance)
+ .map(NarClassLoader.class::cast)
+ .collect(Collectors.toList());
+
+
+ Set actualLibraryLocations = narClassLoaders.stream()
+ .map(classLoader -> classLoader.findLibrary("testjni"))
+ .collect(Collectors.toSet());
+
+ for (NarClassLoader narClassLoader : narClassLoaders) {
+ Class> TestJNI = narClassLoader.loadClass("org.apache.nifi.nar.sharedlib.TestJNI");
+
+ Object actualJniMethodReturnValue = TestJNI
+ .getMethod("testJniMethod")
+ .invoke(TestJNI.newInstance());
+
+ assertEquals("calledNativeTestJniMethod", actualJniMethodReturnValue);
+ }
+
+ assertEquals(1, actualLibraryLocations.size());
+ assertThat(actualLibraryLocations, hasItem(containsString("nifi-nar_without_native_lib-1")));
+ }
+
+ @Test
+ public void testLoadSameLibraryBy2InstanceClassLoadersFromSystemProperty() throws Exception {
+ final File extensionsDir = new File(EXTENSIONS_DIR);
+ final Path narAutoLoadDir = Paths.get(NAR_AUTOLOAD_DIR);
+ for (final File extensionFile : extensionsDir.listFiles()) {
+ Files.copy(extensionFile.toPath(), narAutoLoadDir.resolve(extensionFile.getName()), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ final List narFiles = Arrays.asList(narAutoLoadDir.toFile().listFiles());
+ assertEquals(1, narFiles.size());
+
+ final NarLoadResult narLoadResult = narLoader.load(narFiles);
+ assertNotNull(narLoadResult);
+
+ Bundle bundleWithNativeLib = this.narClassLoaders.getBundles().stream()
+ .filter(bundle -> bundle.getBundleDetails().getCoordinate().getCoordinate().contains("nifi-nar_without_native_lib-"))
+ .findFirst().get();
+
+ Class> processorClass = bundleWithNativeLib.getClassLoader().loadClass("org.apache.nifi.nar.ModifiesClasspathProcessor");
+
+ List instanceClassLoaders = Arrays.asList(
+ extensionManager.createInstanceClassLoader(processorClass.getName(), UUID.randomUUID().toString(), bundleWithNativeLib, null),
+ extensionManager.createInstanceClassLoader(processorClass.getName(), UUID.randomUUID().toString(), bundleWithNativeLib, null)
+ );
+
+ for (InstanceClassLoader instanceClassLoader : instanceClassLoaders) {
+ String actualLibraryLocation = instanceClassLoader.findLibrary("testjni");
+
+ Class> TestJNI = instanceClassLoader.loadClass("org.apache.nifi.nar.sharedlib.TestJNI");
+
+
+ Object actualJniMethodReturnValue = TestJNI
+ .getMethod("testJniMethod")
+ .invoke(TestJNI.newInstance());
+
+ assertThat(actualLibraryLocation, containsString(instanceClassLoader.getIdentifier()));
+ assertEquals("calledNativeTestJniMethod", actualJniMethodReturnValue);
+ }
+ }
+
+ @Override
+ String getWorkDir() {
+ return WORK_DIR;
+ }
+
+ @Override
+ String getNarAutoloadDir() {
+ return NAR_AUTOLOAD_DIR;
+ }
+
+ @Override
+ String getPropertiesFile() {
+ return PROPERTIES_FILE;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestNarLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestNarLoader.java
index 66cb829fe3..8fdb4b383a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestNarLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestNarLoader.java
@@ -16,86 +16,29 @@
*/
package org.apache.nifi.nar;
-import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.reporting.ReportingTask;
-import org.apache.nifi.util.NiFiProperties;
-import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
-import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-public class TestNarLoader {
+public class TestNarLoader extends AbstractTestNarLoader {
static final String WORK_DIR = "./target/work";
static final String NAR_AUTOLOAD_DIR = "./target/extensions";
+ static final String PROPERTIES_FILE = "./src/test/resources/conf/nifi.properties";
static final String EXTENSIONS_DIR = "./src/test/resources/extensions";
- private NiFiProperties properties;
- private ExtensionMapping extensionMapping;
-
- private StandardNarLoader narLoader;
- private NarClassLoaders narClassLoaders;
- private ExtensionDiscoveringManager extensionManager;
-
- @Before
- public void setup() throws IOException, ClassNotFoundException {
- deleteDir(WORK_DIR);
- deleteDir(NAR_AUTOLOAD_DIR);
-
- final File extensionsDir = new File(NAR_AUTOLOAD_DIR);
- assertTrue(extensionsDir.mkdirs());
-
- // Create NiFiProperties
- final String propertiesFile = "./src/test/resources/conf/nifi.properties";
- properties = NiFiProperties.createBasicNiFiProperties(propertiesFile , Collections.emptyMap());
-
- // Unpack NARs
- final Bundle systemBundle = SystemBundle.create(properties);
- extensionMapping = NarUnpacker.unpackNars(properties, systemBundle);
- assertEquals(0, extensionMapping.getAllExtensionNames().size());
-
- // Initialize NarClassLoaders
- narClassLoaders = new NarClassLoaders();
- narClassLoaders.init(properties.getFrameworkWorkingDirectory(), properties.getExtensionsWorkingDirectory());
-
- extensionManager = new StandardExtensionDiscoveringManager();
- extensionManager.discoverExtensions(systemBundle, narClassLoaders.getBundles());
-
- // Should have Framework and Jetty NARs loaded here
- assertEquals(2, narClassLoaders.getBundles().size());
-
- // No extensions should be loaded yet
- assertEquals(0, extensionManager.getExtensions(Processor.class).size());
- assertEquals(0, extensionManager.getExtensions(ControllerService.class).size());
- assertEquals(0, extensionManager.getExtensions(ReportingTask.class).size());
-
- // Create class we are testing
- narLoader = new StandardNarLoader(
- properties.getExtensionsWorkingDirectory(),
- properties.getComponentDocumentationWorkingDirectory(),
- narClassLoaders,
- extensionManager,
- extensionMapping,
- (bundles) -> {});
- }
-
@Test
public void testNarLoaderWhenAllAvailable() throws IOException {
// Copy all NARs from src/test/resources/extensions to target/extensions
@@ -166,24 +109,18 @@ public class TestNarLoader {
assertEquals(0, extensionManager.getExtensions(ReportingTask.class).size());
}
- private void deleteDir(String path) throws IOException {
- Path directory = Paths.get(path);
- if (!directory.toFile().exists()) {
- return;
- }
+ @Override
+ String getWorkDir() {
+ return WORK_DIR;
+ }
- Files.walkFileTree(directory, new SimpleFileVisitor() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- Files.delete(file);
- return FileVisitResult.CONTINUE;
- }
+ @Override
+ String getNarAutoloadDir() {
+ return NAR_AUTOLOAD_DIR;
+ }
- @Override
- public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
- Files.delete(dir);
- return FileVisitResult.CONTINUE;
- }
- });
+ @Override
+ String getPropertiesFile() {
+ return PROPERTIES_FILE;
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_with_native_lib.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_with_native_lib.properties
new file mode 100644
index 0000000000..33133b6d3c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_with_native_lib.properties
@@ -0,0 +1,124 @@
+# 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./target/flow.xml.gz
+nifi.flow.configuration.archive.dir=./target/archive/
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=2 sec
+nifi.administrative.yield.duration=30 sec
+
+nifi.reporting.task.configuration.file=./target/reporting-tasks.xml
+nifi.controller.service.configuration.file=./target/controller-services.xml
+nifi.templates.directory=./target/templates
+nifi.ui.banner.text=UI Banner Text
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=./src/test/resources/lib/
+nifi.nar.library.autoload.directory=./target/nars_with_native_lib
+
+nifi.nar.working.directory=./target/work/nar/
+nifi.documentation.working.directory=./target/work/docs/components
+
+# H2 Settings
+nifi.database.directory=./target/database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.flowfile.repository.directory=./target/test-repo
+nifi.flowfile.repository.partitions=1
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.queue.swap.threshold=20000
+nifi.swap.storage.directory=./target/test-repo/swap
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.claim.max.appendable.size=10 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=./target/content_repository
+
+# Provenance Repository Properties
+nifi.provenance.repository.storage.directory=./target/provenance_repository
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+
+# Site to Site properties
+nifi.remote.input.socket.port=9990
+nifi.remote.input.secure=true
+
+# web properties #
+nifi.web.war.directory=./target/lib
+nifi.web.http.host=
+nifi.web.http.port=8080
+nifi.web.https.host=
+nifi.web.https.port=
+nifi.web.jetty.working.directory=./target/work/jetty
+
+# security properties #
+nifi.sensitive.props.key=key
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=
+nifi.security.keystoreType=
+nifi.security.keystorePasswd=
+nifi.security.keyPasswd=
+nifi.security.truststore=
+nifi.security.truststoreType=
+nifi.security.truststorePasswd=
+nifi.security.user.authorizer=
+
+# cluster common properties (cluster manager and nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+nifi.cluster.protocol.socket.timeout=30 sec
+nifi.cluster.protocol.connection.handshake.timeout=45 sec
+# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties must be configured #
+nifi.cluster.protocol.use.multicast=false
+nifi.cluster.protocol.multicast.address=
+nifi.cluster.protocol.multicast.port=
+nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
+nifi.cluster.protocol.multicast.service.locator.attempts=3
+nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=2
+# if multicast is not used, nifi.cluster.node.unicast.xxx must have same values as nifi.cluster.manager.xxx #
+nifi.cluster.node.unicast.manager.address=
+nifi.cluster.node.unicast.manager.protocol.port=
+nifi.cluster.node.unicast.manager.authority.provider.port=
+
+# cluster manager properties (only configure for cluster manager) #
+nifi.cluster.is.manager=false
+nifi.cluster.manager.address=
+nifi.cluster.manager.protocol.port=
+nifi.cluster.manager.authority.provider.port=
+nifi.cluster.manager.authority.provider.threads=10
+nifi.cluster.manager.node.firewall.file=
+nifi.cluster.manager.node.event.history.size=10
+nifi.cluster.manager.node.api.connection.timeout=30 sec
+nifi.cluster.manager.node.api.read.timeout=30 sec
+nifi.cluster.manager.node.api.request.threads=10
+nifi.cluster.manager.flow.retrieval.delay=5 sec
+nifi.cluster.manager.protocol.threads=10
+nifi.cluster.manager.safemode.duration=0 sec
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_without_native_lib.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_without_native_lib.properties
new file mode 100644
index 0000000000..4784446340
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_without_native_lib.properties
@@ -0,0 +1,124 @@
+# 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./target/flow.xml.gz
+nifi.flow.configuration.archive.dir=./target/archive/
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=2 sec
+nifi.administrative.yield.duration=30 sec
+
+nifi.reporting.task.configuration.file=./target/reporting-tasks.xml
+nifi.controller.service.configuration.file=./target/controller-services.xml
+nifi.templates.directory=./target/templates
+nifi.ui.banner.text=UI Banner Text
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=./src/test/resources/lib/
+nifi.nar.library.autoload.directory=./target/nars_without_native_lib
+
+nifi.nar.working.directory=./target/work/nar/
+nifi.documentation.working.directory=./target/work/docs/components
+
+# H2 Settings
+nifi.database.directory=./target/database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.flowfile.repository.directory=./target/test-repo
+nifi.flowfile.repository.partitions=1
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.queue.swap.threshold=20000
+nifi.swap.storage.directory=./target/test-repo/swap
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.claim.max.appendable.size=10 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=./target/content_repository
+
+# Provenance Repository Properties
+nifi.provenance.repository.storage.directory=./target/provenance_repository
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+
+# Site to Site properties
+nifi.remote.input.socket.port=9990
+nifi.remote.input.secure=true
+
+# web properties #
+nifi.web.war.directory=./target/lib
+nifi.web.http.host=
+nifi.web.http.port=8080
+nifi.web.https.host=
+nifi.web.https.port=
+nifi.web.jetty.working.directory=./target/work/jetty
+
+# security properties #
+nifi.sensitive.props.key=key
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=
+nifi.security.keystoreType=
+nifi.security.keystorePasswd=
+nifi.security.keyPasswd=
+nifi.security.truststore=
+nifi.security.truststoreType=
+nifi.security.truststorePasswd=
+nifi.security.user.authorizer=
+
+# cluster common properties (cluster manager and nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+nifi.cluster.protocol.socket.timeout=30 sec
+nifi.cluster.protocol.connection.handshake.timeout=45 sec
+# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties must be configured #
+nifi.cluster.protocol.use.multicast=false
+nifi.cluster.protocol.multicast.address=
+nifi.cluster.protocol.multicast.port=
+nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
+nifi.cluster.protocol.multicast.service.locator.attempts=3
+nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=2
+# if multicast is not used, nifi.cluster.node.unicast.xxx must have same values as nifi.cluster.manager.xxx #
+nifi.cluster.node.unicast.manager.address=
+nifi.cluster.node.unicast.manager.protocol.port=
+nifi.cluster.node.unicast.manager.authority.provider.port=
+
+# cluster manager properties (only configure for cluster manager) #
+nifi.cluster.is.manager=false
+nifi.cluster.manager.address=
+nifi.cluster.manager.protocol.port=
+nifi.cluster.manager.authority.provider.port=
+nifi.cluster.manager.authority.provider.threads=10
+nifi.cluster.manager.node.firewall.file=
+nifi.cluster.manager.node.event.history.size=10
+nifi.cluster.manager.node.api.connection.timeout=30 sec
+nifi.cluster.manager.node.api.read.timeout=30 sec
+nifi.cluster.manager.node.api.request.threads=10
+nifi.cluster.manager.flow.retrieval.delay=5 sec
+nifi.cluster.manager.protocol.threads=10
+nifi.cluster.manager.safemode.duration=0 sec
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-1-1.0.nar b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-1-1.0.nar
new file mode 100644
index 0000000000..2d62b0cf2a
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-1-1.0.nar differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-2-1.0.nar b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-2-1.0.nar
new file mode 100644
index 0000000000..0e87d9445a
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_with_native_lib/nifi-nar_with_native_lib-2-1.0.nar differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_without_native_lib/nifi-nar_without_native_lib-1-1.0.nar b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_without_native_lib/nifi-nar_without_native_lib-1-1.0.nar
new file mode 100644
index 0000000000..b9c7419d3e
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/nars_without_native_lib/nifi-nar_without_native_lib-1-1.0.nar differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/TestJNI.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/TestJNI.java
new file mode 100644
index 0000000000..781c05ef8b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/TestJNI.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.apache.nifi.nar.sharedlib;
+
+public class TestJNI {
+ static {
+ System.loadLibrary("testjni");
+ }
+
+ public native String testJniMethod();
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/libtestjni.dylib b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/libtestjni.dylib
new file mode 100644
index 0000000000..a06ff7c74f
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/libtestjni.dylib differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.cpp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.cpp
new file mode 100644
index 0000000000..d58496c773
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.cpp
@@ -0,0 +1,24 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_apache_nifi_nar_sharedlib_TestJNI */
+
+#ifndef _Included_org_apache_nifi_nar_sharedlib_TestJNI
+#define _Included_org_apache_nifi_nar_sharedlib_TestJNI
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_apache_nifi_nar_sharedlib_TestJNI
+ * Method: testJniMethod
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_apache_nifi_nar_sharedlib_TestJNI_testJniMethod
+ (JNIEnv *env, jobject thisObject) {
+ jstring result = (*env).NewStringUTF("calledNativeTestJniMethod");
+ return result;
+ }
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.h b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.h
new file mode 100644
index 0000000000..1d75087105
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_apache_nifi_nar_sharedlib_TestJNI */
+
+#ifndef _Included_org_apache_nifi_nar_sharedlib_TestJNI
+#define _Included_org_apache_nifi_nar_sharedlib_TestJNI
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_apache_nifi_nar_sharedlib_TestJNI
+ * Method: testJniMethod
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_apache_nifi_nar_sharedlib_TestJNI_testJniMethod
+ (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.o b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.o
new file mode 100755
index 0000000000..3a71bf2953
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.o differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
index d9e23fa216..bf78768bd0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
@@ -19,10 +19,14 @@ package org.apache.nifi.nar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.net.URISyntaxException;
import java.net.URL;
-import java.net.URLClassLoader;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -31,7 +35,7 @@ import java.util.Set;
* The InstanceClassLoader will either be an empty pass-through to the NARClassLoader, or will contain a
* copy of all the NAR's resources in the case of components that @RequireInstanceClassLoading.
*/
-public class InstanceClassLoader extends URLClassLoader {
+public class InstanceClassLoader extends AbstractNativeLibHandlingClassLoader {
private static final Logger logger = LoggerFactory.getLogger(InstanceClassLoader.class);
@@ -48,7 +52,18 @@ public class InstanceClassLoader extends URLClassLoader {
* @param parent the parent ClassLoader
*/
public InstanceClassLoader(final String identifier, final String type, final Set instanceUrls, final Set additionalResourceUrls, final ClassLoader parent) {
- super(combineURLs(instanceUrls, additionalResourceUrls), parent);
+ this(identifier, type, instanceUrls, additionalResourceUrls, Collections.emptySet(), parent);
+ }
+
+ public InstanceClassLoader(
+ final String identifier,
+ final String type,
+ final Set instanceUrls,
+ final Set additionalResourceUrls,
+ final Set narNativeLibDirs,
+ final ClassLoader parent
+ ) {
+ super(combineURLs(instanceUrls, additionalResourceUrls), parent, initNativeLibDirList(narNativeLibDirs, additionalResourceUrls), identifier);
this.identifier = identifier;
this.instanceType = type;
this.instanceUrls = Collections.unmodifiableSet(
@@ -57,6 +72,35 @@ public class InstanceClassLoader extends URLClassLoader {
additionalResourceUrls == null ? Collections.emptySet() : new LinkedHashSet<>(additionalResourceUrls));
}
+ private static List initNativeLibDirList(Set narNativeLibDirs, Set additionalResourceUrls) {
+ List nativeLibDirList = new ArrayList<>(narNativeLibDirs);
+
+ Set additionalNativeLibDirs = new HashSet<>();
+ if (additionalResourceUrls != null) {
+ for (URL url : additionalResourceUrls) {
+ File file;
+
+ try {
+ file = new File(url.toURI());
+ } catch (URISyntaxException e) {
+ file = new File(url.getPath());
+ } catch (Exception e) {
+ logger.error("Couldn't convert url '" + url + "' to a file");
+ file = null;
+ }
+
+ File dir = toDir(file);
+ if (dir != null) {
+ additionalNativeLibDirs.add(dir);
+ }
+ }
+ }
+
+ nativeLibDirList.addAll(additionalNativeLibDirs);
+
+ return nativeLibDirList;
+ }
+
private static URL[] combineURLs(final Set instanceUrls, final Set additionalResourceUrls) {
final Set allUrls = new LinkedHashSet<>();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
index f037569c20..f7804004e7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.nar;
+import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -367,6 +368,9 @@ public class StandardExtensionDiscoveringManager implements ExtensionDiscovering
logger.debug("Including ClassLoader resources from {} for component {}", new Object[] {bundle.getBundleDetails(), instanceIdentifier});
final Set instanceUrls = new LinkedHashSet<>();
+ final Set narNativeLibDirs = new LinkedHashSet<>();
+
+ narNativeLibDirs.add(narBundleClassLoader.getNARNativeLibDir());
instanceUrls.addAll(Arrays.asList(narBundleClassLoader.getURLs()));
ClassLoader ancestorClassLoader = narBundleClassLoader.getParent();
@@ -385,12 +389,15 @@ public class StandardExtensionDiscoveringManager implements ExtensionDiscovering
}
final NarClassLoader ancestorNarClassLoader = (NarClassLoader) ancestorClassLoader;
+
+ narNativeLibDirs.add(ancestorNarClassLoader.getNARNativeLibDir());
Collections.addAll(instanceUrls, ancestorNarClassLoader.getURLs());
+
ancestorClassLoader = ancestorNarClassLoader.getParent();
}
}
- instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, instanceUrls, additionalUrls, ancestorClassLoader);
+ instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, instanceUrls, additionalUrls, narNativeLibDirs, ancestorClassLoader);
} else {
instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, Collections.emptySet(), additionalUrls, bundleClassLoader);
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoader.java
new file mode 100644
index 0000000000..4edbe9d853
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoader.java
@@ -0,0 +1,190 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
+/**
+ * An extension of {@link URLClassLoader} that can load native libraries from a
+ * predefined list of directories as well as from those that are defined by
+ * the java.library.path system property.
+ *
+ * Once a library is found an OS-handled temporary copy is created and cached
+ * to maintain consistency and classloader isolation.
+ */
+public abstract class AbstractNativeLibHandlingClassLoader extends URLClassLoader implements OSUtil {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * Directories in which to look for native libraries
+ */
+ protected final List nativeLibDirList;
+ /**
+ * Used to cache (the paths of) temporary copies of loaded libraries
+ */
+ protected final Map nativeLibNameToPath = new HashMap<>();
+ /**
+ * Used as prefix when creating the temporary copies of libraries
+ */
+ private final String tmpLibFilePrefix;
+
+ public AbstractNativeLibHandlingClassLoader(URL[] urls, List initialNativeLibDirList, String tmpLibFilePrefix) {
+ super(urls);
+
+ this.nativeLibDirList = buildNativeLibDirList(initialNativeLibDirList);
+ this.tmpLibFilePrefix = tmpLibFilePrefix;
+ }
+
+ public AbstractNativeLibHandlingClassLoader(URL[] urls, ClassLoader parent, List initialNativeLibDirList, String tmpLibFilePrefix) {
+ super(urls, parent);
+
+ this.nativeLibDirList = buildNativeLibDirList(initialNativeLibDirList);
+ this.tmpLibFilePrefix = tmpLibFilePrefix;
+ }
+
+ public static File toDir(File fileOrDir) {
+ if (fileOrDir == null) {
+ return null;
+ } else if (fileOrDir.isFile()) {
+ return fileOrDir.getParentFile();
+ } else if (fileOrDir.isDirectory()) {
+ return fileOrDir;
+ } else {
+ return null;
+ }
+ }
+
+ public String findLibrary(String libname) {
+ String libLocationString;
+
+ Path libLocation = nativeLibNameToPath.compute(
+ libname,
+ (__, currentLocation) -> {
+ if (currentLocation != null && currentLocation.toFile().exists()) {
+ return currentLocation;
+ } else {
+ for (File nativeLibDir : nativeLibDirList) {
+ String libraryOriginalPathString = findLibrary(libname, nativeLibDir);
+ if (libraryOriginalPathString != null) {
+ return createTempCopy(libname, libraryOriginalPathString);
+ }
+ }
+
+ return null;
+ }
+ }
+ );
+
+ if (libLocation == null) {
+ libLocationString = null;
+ } else {
+ libLocationString = libLocation.toFile().getAbsolutePath();
+ }
+
+ return libLocationString;
+ }
+
+ protected Set getUsrLibDirs() {
+ Set usrLibDirs = Arrays.stream(getJavaLibraryPath().split(File.pathSeparator))
+ .map(String::trim)
+ .filter(pathAsString -> !pathAsString.isEmpty())
+ .map(File::new)
+ .map(AbstractNativeLibHandlingClassLoader::toDir)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ return usrLibDirs;
+ }
+
+ protected String getJavaLibraryPath() {
+ return System.getProperty("java.library.path", "");
+ }
+
+ protected Path createTempCopy(String libname, String libraryOriginalPathString) {
+ Path tempFile;
+
+ try {
+ tempFile = Files.createTempFile(tmpLibFilePrefix + "_", "_" + libname);
+ Files.copy(Paths.get(libraryOriginalPathString), tempFile, REPLACE_EXISTING);
+ } catch (Exception e) {
+ logger.error("Couldn't create temporary copy of the library '" + libname + "' found at '" + libraryOriginalPathString + "'", e);
+
+ tempFile = null;
+ }
+
+ return tempFile;
+ }
+
+ protected String findLibrary(String libname, File nativeLibDir) {
+ final File dllFile = new File(nativeLibDir, libname + ".dll");
+ final File dylibFile = new File(nativeLibDir, libname + ".dylib");
+ final File libdylibFile = new File(nativeLibDir, "lib" + libname + ".dylib");
+ final File libsoFile = new File(nativeLibDir, "lib" + libname + ".so");
+ final File soFile = new File(nativeLibDir, libname + ".so");
+
+ if (isOsWindows() && dllFile.exists()) {
+ return dllFile.getAbsolutePath();
+ } else if (isOsMac()) {
+ if (dylibFile.exists()) {
+ return dylibFile.getAbsolutePath();
+ } else if (libdylibFile.exists()) {
+ return libdylibFile.getAbsolutePath();
+ } else if (soFile.exists()) {
+ return soFile.getAbsolutePath();
+ } else if (libsoFile.exists()) {
+ return libsoFile.getAbsolutePath();
+ }
+ } else if (isOsLinuxUnix()) {
+ if (soFile.exists()) {
+ return soFile.getAbsolutePath();
+ } else if (libsoFile.exists()) {
+ return libsoFile.getAbsolutePath();
+ }
+ }
+
+ // not found in the nar. try system native dir
+ return null;
+ }
+
+ private List buildNativeLibDirList(List initialNativeLibDirList) {
+ List allNativeLibDirList = new ArrayList<>(initialNativeLibDirList);
+
+ allNativeLibDirList.addAll(getUsrLibDirs());
+
+ return Collections.unmodifiableList(allNativeLibDirList);
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
index 776ec28bbd..5c1abe281f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
@@ -20,9 +20,10 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
-import java.net.URLClassLoader;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,12 +53,17 @@ import org.slf4j.LoggerFactory;
*
*
* +META-INF/
- * +-- bundled-dependencies/
+ * +-- bundled-dependencies/[native]
* +-- <JAR files>
* +-- MANIFEST.MF
*
*
*
+ * The optional "native" subdirectory under "bundled-dependencies" may contain native
+ * libraries. Directories defined via the java.library.path system property are also scanned.
+ * After a library is found an OS-handled temporary copy is created and cached before loading
+ * it to maintain consistency and classloader isolation.
+ *
*
* The MANIFEST.MF file contains the same information as a typical JAR file but
* also includes two additional NiFi properties: {@code Nar-Id} and
@@ -116,7 +122,7 @@ import org.slf4j.LoggerFactory;
* Maven NAR plugin will fail to build the NAR.
*
*/
-public class NarClassLoader extends URLClassLoader {
+public class NarClassLoader extends AbstractNativeLibHandlingClassLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(NarClassLoader.class);
@@ -144,7 +150,7 @@ public class NarClassLoader extends URLClassLoader {
* @throws IOException if an error occurs while loading the NAR.
*/
public NarClassLoader(final File narWorkingDirectory) throws ClassNotFoundException, IOException {
- super(new URL[0]);
+ super(new URL[0], initNativeLibDirList(narWorkingDirectory), narWorkingDirectory.getName());
this.narWorkingDirectory = narWorkingDirectory;
// process the classpath
@@ -163,7 +169,7 @@ public class NarClassLoader extends URLClassLoader {
* @throws IOException if an error occurs while loading the NAR.
*/
public NarClassLoader(final File narWorkingDirectory, final ClassLoader parentClassLoader) throws ClassNotFoundException, IOException {
- super(new URL[0], parentClassLoader);
+ super(new URL[0], parentClassLoader, initNativeLibDirList(narWorkingDirectory), narWorkingDirectory.getName());
this.narWorkingDirectory = narWorkingDirectory;
// process the classpath
@@ -204,27 +210,25 @@ public class NarClassLoader extends URLClassLoader {
}
}
- @Override
- protected String findLibrary(final String libname) {
+ public File getNARNativeLibDir() {
+ return getNARNativeLibDir(narWorkingDirectory);
+ }
+
+ private static List initNativeLibDirList(File narWorkingDirectory) {
+ ArrayList nativeLibDirList = new ArrayList<>();
+
+ nativeLibDirList.add(getNARNativeLibDir(narWorkingDirectory));
+
+ return nativeLibDirList;
+ }
+
+ private static File getNARNativeLibDir(File narWorkingDirectory) {
File dependencies = new File(narWorkingDirectory, "NAR-INF/bundled-dependencies");
if (!dependencies.isDirectory()) {
LOGGER.warn(narWorkingDirectory + " does not contain NAR-INF/bundled-dependencies!");
}
- final File nativeDir = new File(dependencies, "native");
- final File libsoFile = new File(nativeDir, "lib" + libname + ".so");
- final File dllFile = new File(nativeDir, libname + ".dll");
- final File soFile = new File(nativeDir, libname + ".so");
- if (libsoFile.exists()) {
- return libsoFile.getAbsolutePath();
- } else if (dllFile.exists()) {
- return dllFile.getAbsolutePath();
- } else if (soFile.exists()) {
- return soFile.getAbsolutePath();
- }
-
- // not found in the nar. try system native dir
- return null;
+ return new File(dependencies, "native");
}
@Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/OSUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/OSUtil.java
new file mode 100644
index 0000000000..83f54e3df2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/OSUtil.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+public interface OSUtil {
+ String OS = System.getProperty("os.name").toLowerCase();
+
+ default boolean isOsWindows() {
+ return OS.contains("win");
+ }
+
+ default boolean isOsMac() {
+ return OS.contains("mac");
+ }
+
+ default boolean isOsLinuxUnix() {
+ return OS.contains("nix") || OS.contains("nux") || OS.contains("aix");
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/test/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoaderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/test/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoaderTest.java
new file mode 100644
index 0000000000..01fd49f818
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/test/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoaderTest.java
@@ -0,0 +1,540 @@
+/*
+ * 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 org.apache.nifi.nar;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+public class AbstractNativeLibHandlingClassLoaderTest {
+ public static final String NATIVE_LIB_NAME = "native_lib";
+
+ @Mock
+ private AbstractNativeLibHandlingClassLoader testSubjectHelper;
+
+ private Path tempDirectory;
+
+ private String javaLibraryPath = "";
+
+ private List nativeLibDirs = new ArrayList<>();
+ private final Map nativeLibNameToPath = new HashMap<>();
+
+ private boolean isOsWindows;
+ private boolean isOsMaxOsx;
+ private boolean isOsLinux;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ tempDirectory = Files.createTempDirectory(this.getClass().getSimpleName());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tempDirectory.toFile().deleteOnExit();
+
+ Files.walk(tempDirectory)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnNullOnWindowsWhenNoDLLAvailable() throws Exception {
+ // GIVEN
+ isOsWindows = true;
+
+ createTempFile("so");
+ createTempFile("lib", "so");
+ createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = null;
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnDLLOnWindows() throws Exception {
+ // GIVEN
+ isOsWindows = true;
+
+ Path expectedNativeLib = createTempFile("dll");
+ createTempFile("so");
+ createTempFile("lib", "so");
+ createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnNullOnMacWhenNoDylibOrSoAvailable() throws Exception {
+ // GIVEN
+ isOsMaxOsx = true;
+
+ createTempFile("dll");
+
+ String expected = null;
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnDylibOnMac() throws Exception {
+ // GIVEN
+ isOsMaxOsx = true;
+
+ createTempFile("dll");
+ createTempFile("so");
+ createTempFile("lib", "so");
+ Path expectedNativeLib = createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnLibDylibOnMac() throws Exception {
+ // GIVEN
+ isOsMaxOsx = true;
+
+ createTempFile("dll");
+ createTempFile("so");
+ createTempFile("lib", "so");
+ Path expectedNativeLib = createTempFile("lib", "dylib");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryMayReturnSoOnMac() throws Exception {
+ // GIVEN
+ isOsMaxOsx = true;
+
+ createTempFile("dll");
+ Path expectedNativeLib = createTempFile("so");
+ createTempFile("lib", "so");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryMayReturnLibSoOnMac() throws Exception {
+ // GIVEN
+ isOsMaxOsx = true;
+
+ createTempFile("dll");
+ Path expectedNativeLib = createTempFile("lib", "so");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnNullOnLinuxWhenNoSoAvailable() throws Exception {
+ // GIVEN
+ isOsLinux = true;
+
+ createTempFile("dll");
+ createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = null;
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnSoOnLinux() throws Exception {
+ // GIVEN
+ isOsLinux = true;
+
+ createTempFile("dll");
+ Path expectedNativeLib = createTempFile("so");
+ createTempFile("lib", "so");
+ createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnLibSoOnLinux() throws Exception {
+ // GIVEN
+ isOsLinux = true;
+
+ createTempFile("dll");
+ Path expectedNativeLib = createTempFile("lib", "so");
+ createTempFile("dylib");
+ createTempFile("lib", "dylib");
+
+ String expected = expectedNativeLib.toFile().getAbsolutePath();
+
+ // WHEN
+ // THEN
+ testFindLibrary(expected);
+ }
+
+ private void testFindLibrary(String expected) {
+ String actual = createTestSubjectForOS().findLibrary(NATIVE_LIB_NAME, tempDirectory.toFile());
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnLibLocation() throws Exception {
+ // GIVEN
+ File nativeLibDir = mock(File.class);
+
+ nativeLibDirs = Arrays.asList(nativeLibDir);
+
+ Path libPath = createTempFile("mocked").toAbsolutePath();
+ when(testSubjectHelper.findLibrary("libName", nativeLibDir)).thenReturn("libLocation");
+ when(testSubjectHelper.createTempCopy("libName", "libLocation")).thenReturn(libPath);
+
+ String expected = libPath.toFile().getAbsolutePath();
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+
+ // WHEN
+ String actual = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual);
+ verify(testSubjectHelper).findLibrary("libName", nativeLibDir);
+ verify(testSubjectHelper).createTempCopy("libName", "libLocation");
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnFirstFoundLibLocation() throws Exception {
+ // GIVEN
+ File nativeLibDir1 = mock(File.class);
+ File nativeLibDir2 = mock(File.class);
+ File nativeLibDir3 = mock(File.class);
+
+ nativeLibDirs = Arrays.asList(nativeLibDir1, nativeLibDir2, nativeLibDir3);
+
+ Path libPath = createTempFile("mocked").toAbsolutePath();
+ when(testSubjectHelper.findLibrary("libName", nativeLibDir1)).thenReturn(null);
+ when(testSubjectHelper.findLibrary("libName", nativeLibDir2)).thenReturn("firstFoundLibLocation");
+ when(testSubjectHelper.createTempCopy("libName", "firstFoundLibLocation")).thenReturn(libPath);
+
+ String expected = libPath.toFile().getAbsolutePath();
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+
+ // WHEN
+ String actual = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual);
+ verify(testSubjectHelper).findLibrary("libName", nativeLibDir1);
+ verify(testSubjectHelper).findLibrary("libName", nativeLibDir2);
+ verify(testSubjectHelper).createTempCopy("libName", "firstFoundLibLocation");
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnCachedLibLocation() throws Exception {
+ // GIVEN
+ File nativeLibDir = mock(File.class);
+
+ nativeLibDirs = Arrays.asList(nativeLibDir);
+
+ Path cachedLibPath = createTempFile("cached", "mocked").toAbsolutePath();
+ nativeLibNameToPath.put("libName", cachedLibPath);
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+ String expected = cachedLibPath.toFile().getAbsolutePath();
+
+ // WHEN
+ String actual = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual);
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnFoundThenCachedLibLocation() throws Exception {
+ // GIVEN
+ File nativeLibDir = mock(File.class);
+
+ nativeLibDirs = Arrays.asList(nativeLibDir);
+
+ Path libPath = createTempFile("mocked").toAbsolutePath();
+ when(testSubjectHelper.findLibrary("libName", nativeLibDir)).thenReturn("libLocation");
+ when(testSubjectHelper.createTempCopy("libName", "libLocation")).thenReturn(libPath);
+
+ String expected = libPath.toFile().getAbsolutePath();
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+
+ // WHEN
+ String actual1 = testSubject.findLibrary("libName");
+ String actual2 = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual1);
+ assertEquals(expected, actual2);
+ verify(testSubjectHelper).findLibrary("libName", nativeLibDir);
+ verify(testSubjectHelper).createTempCopy("libName", "libLocation");
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnNullWhenLibDirNotRegistered() throws Exception {
+ // GIVEN
+ nativeLibDirs = new ArrayList<>();
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+ String expected = null;
+
+ // WHEN
+ String actual = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual);
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testFindLibraryShouldReturnNullWhenLibNotFound() throws Exception {
+ // GIVEN
+ File nativeLibDir = mock(File.class);
+
+ nativeLibDirs = Arrays.asList(nativeLibDir);
+
+ when(testSubjectHelper.findLibrary("libName", nativeLibDir)).thenReturn(null);
+
+ AbstractNativeLibHandlingClassLoader testSubject = createTestSubject();
+ String expected = null;
+
+ // WHEN
+ String actual = testSubject.findLibrary("libName");
+
+ // THEN
+ assertEquals(expected, actual);
+ verify(testSubjectHelper).findLibrary("libName", nativeLibDir);
+ verifyNoMoreInteractions(testSubjectHelper);
+ }
+
+ @Test
+ public void testToDirShouldReturnNullForNullInput() throws Exception {
+ // GIVEN
+ File expected = null;
+
+ // WHEN
+ File actual = createTestSubject().toDir(null);
+
+ // THEN
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testToDirShouldReturnParentForFile() throws Exception {
+ // GIVEN
+ Path filePath = createTempFile("mocked").toAbsolutePath();
+ File expected = filePath.getParent().toFile();
+
+ // WHEN
+ File actual = createTestSubject().toDir(filePath.toFile());
+
+ // THEN
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testToDirShouldReturnDirUnchanged() throws Exception {
+ // GIVEN
+ Path dirPath = createTempFile("mocked").getParent();
+ File expected = dirPath.toFile();
+
+ // WHEN
+ File actual = createTestSubject().toDir(dirPath.toFile());
+
+ // THEN
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testGetUsrLibDirsShouldReturnUniqueDirs() throws Exception {
+ Path dir1 = Files.createDirectory(tempDirectory.resolve("dir1"));
+ Path dir2 = Files.createDirectory(tempDirectory.resolve("dir2"));
+ Path dir3 = Files.createDirectory(tempDirectory.resolve("dir3"));
+ Path dir4 = Files.createDirectory(tempDirectory.resolve("dir4"));
+
+ Path file11 = createTempFile(dir1, "usrLib", "file11");
+ Path file12 = createTempFile(dir1, "usrLib", "file12");
+ Path file21 = createTempFile(dir2, "usrLib", "file21");
+ Path file31 = createTempFile(dir3, "usrLib", "file31");
+
+ javaLibraryPath = Stream.of(
+ file11,
+ file12,
+ file21,
+ file31,
+ dir3,
+ dir4
+ )
+ .map(Path::toFile)
+ .map(File::getAbsolutePath)
+ .collect(Collectors.joining(File.pathSeparator));
+
+ HashSet expected = new HashSet<>();
+ expected.add(dir1.toFile());
+ expected.add(dir2.toFile());
+ expected.add(dir3.toFile());
+ expected.add(dir4.toFile());
+
+ Set actual = createTestSubject().getUsrLibDirs();
+
+ assertEquals(expected, actual);
+ }
+
+ private AbstractNativeLibHandlingClassLoader createTestSubjectForOS() {
+ AbstractNativeLibHandlingClassLoader testSubject = new AbstractNativeLibHandlingClassLoader(new URL[0], nativeLibDirs, "unimportant") {
+ @Override
+ public boolean isOsWindows() {
+ return isOsWindows;
+ }
+
+ @Override
+ public boolean isOsMac() {
+ return isOsMaxOsx;
+ }
+
+ @Override
+ public boolean isOsLinuxUnix() {
+ return isOsLinux;
+ }
+
+ @Override
+ public String getJavaLibraryPath() {
+ return javaLibraryPath;
+ }
+ };
+
+ return testSubject;
+ }
+
+ private AbstractNativeLibHandlingClassLoader createTestSubject() {
+ AbstractNativeLibHandlingClassLoader testSubject = new AbstractNativeLibHandlingClassLoader(new URL[0], nativeLibDirs, "unimportant") {
+ @Override
+ public Path createTempCopy(String libname, String libraryOriginalPathString) {
+ return testSubjectHelper.createTempCopy(libname, libraryOriginalPathString);
+ }
+
+ @Override
+ public String findLibrary(String libname, File nativeLibDir) {
+ return testSubjectHelper.findLibrary(libname, nativeLibDir);
+ }
+
+ @Override
+ public boolean isOsWindows() {
+ return isOsWindows;
+ }
+
+ @Override
+ public boolean isOsMac() {
+ return isOsMaxOsx;
+ }
+
+ @Override
+ public boolean isOsLinuxUnix() {
+ return isOsLinux;
+ }
+
+ @Override
+ public String getJavaLibraryPath() {
+ return javaLibraryPath;
+ }
+ };
+
+ testSubject.nativeLibNameToPath.putAll(this.nativeLibNameToPath);
+
+ return testSubject;
+ }
+
+ private Path createTempFile(String suffix) throws IOException {
+ return createTempFile("", suffix);
+ }
+
+ private Path createTempFile(String prefix, String suffix) throws IOException {
+ return createTempFile(tempDirectory, prefix, suffix);
+ }
+
+ private Path createTempFile(Path tempDirectory, String prefix, String suffix) throws IOException {
+ return Files.createFile(tempDirectory.resolve(prefix + NATIVE_LIB_NAME + "." + suffix));
+ }
+}