From b7fb94723c06ef0277ae0c274f6dd318c90f00ea Mon Sep 17 00:00:00 2001 From: Tamas Palfy Date: Tue, 19 Nov 2019 15:53:27 +0100 Subject: [PATCH] NIFI-6884 - Native library loading fixed/improved: NarClassLoader and InstanceClassLoader can load libraries from their own or their ancestors' NAR-INF/bundled-dependencies/native directory. They also scan directories defined via java.library.path system property. InstanceClassLoader also checks additional classpath resources defined by PropertyDescriptors with "dynamicallyModifiesClasspath(true)". Added tests for loading native libraries. Supports mac only. Added support for loading native libs from additional resources in AbstractHadoopProcessor. Updated javadoc for PropertyDescriptor.dynamicallyModifiesClasspath. This closes #3894. Signed-off-by: Mark Payne --- .../nifi/components/PropertyDescriptor.java | 20 +- .../src/main/asciidoc/developer-guide.adoc | 3 + .../hadoop/AbstractHadoopProcessor.java | 4 +- .../nifi-framework-core-api/pom.xml | 4 + .../controller/TestAbstractComponentNode.java | 4 +- .../controller/TestStandardProcessorNode.java | 102 +++- .../test/resources/native/libtestjni.dylib | Bin 0 -> 8560 bytes .../nifi/nar/AbstractTestNarLoader.java | 114 ++++ .../nifi/nar/TestLoadNativeLibFromNar.java | 145 +++++ .../TestLoadNativeLibViaSystemProperty.java | 160 ++++++ .../org/apache/nifi/nar/TestNarLoader.java | 89 +-- .../conf/nifi.nar_with_native_lib.properties | 124 ++++ .../nifi.nar_without_native_lib.properties | 124 ++++ .../nifi-nar_with_native_lib-1-1.0.nar | Bin 0 -> 7246 bytes .../nifi-nar_with_native_lib-2-1.0.nar | Bin 0 -> 7258 bytes .../nifi-nar_without_native_lib-1-1.0.nar | Bin 0 -> 6326 bytes .../src/test/resources/native/TestJNI.java | 25 + .../test/resources/native/libtestjni.dylib | Bin 0 -> 8560 bytes .../org_apache_nifi_nar_sharedlib_TestJNI.cpp | 24 + .../org_apache_nifi_nar_sharedlib_TestJNI.h | 21 + .../org_apache_nifi_nar_sharedlib_TestJNI.o | Bin 0 -> 1012 bytes .../apache/nifi/nar/InstanceClassLoader.java | 50 +- .../StandardExtensionDiscoveringManager.java | 9 +- .../AbstractNativeLibHandlingClassLoader.java | 190 ++++++ .../org/apache/nifi/nar/NarClassLoader.java | 46 +- .../main/java/org/apache/nifi/nar/OSUtil.java | 33 ++ ...tractNativeLibHandlingClassLoaderTest.java | 540 ++++++++++++++++++ 27 files changed, 1715 insertions(+), 116 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/native/libtestjni.dylib create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/AbstractTestNarLoader.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibFromNar.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/java/org/apache/nifi/nar/TestLoadNativeLibViaSystemProperty.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_with_native_lib.properties create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/conf/nifi.nar_without_native_lib.properties create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/TestJNI.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/libtestjni.dylib create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.cpp create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.h create mode 100755 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-loading-utils/src/test/resources/native/org_apache_nifi_nar_sharedlib_TestJNI.o create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoader.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/OSUtil.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/test/java/org/apache/nifi/nar/AbstractNativeLibHandlingClassLoaderTest.java 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 0000000000000000000000000000000000000000..a06ff7c74f94e0f5d06a9c39bcda45317c97c943 GIT binary patch literal 8560 zcmeHNTWb?R6rOFOwzamYPhQJbsHlb37B77eNu`lZTSH4z@Pdq+WZSHyn>4#=n+G9M z1VgAS{sMo1Kfos~T71< z{B?v7B|r!{4B8I*DoDt)QYTo1oCN)%5JG7@ayQOGjUS;#mvA32$sw+oL8)UbIcU|z zj~D8w$J4wYeJEo`f~2(LAfD|M683h;kGI6}a84-b!!)*~nd6E6Iv!0MnVgY_EI(d} z*y4Syf>Js%93RFA7cs$oTD-6LtO#sMtqiq`bBSz*=A8V#cmfWe4;(K9`S;!z zN;CRFcp(nd%mL8%;)(hMKF$+4p>#YN8;guY#WSq(3{e>ObG#VD@z!+={L7#xeDRE# za17gdY?*zjA~?{^}b$S3x%g3spDU5_7y*aWCo zFc($v27UIr>ihoi{?pLnbrLVa(eKv=?mZvxti5ena|R)%f-=nZR`?-kWj3oN@LstD zeIZaGZD8R15DRMwwzx1kX9 z<*EU=Ra{4ChU&vesq^efl(8?ciT{Bp?r)oX((7H)IlQB{;-7y!8I4mqiE%T1yq_ZjL#CP*EpeWuJ-@kFad<9z6 zB3}!ljqmSmFfznOgTr6(nYG79n2+6~ONJx^k^#wpWI!??8ITM}1|$QL0m*=5Kr$d1 z_)i(=><&$0>Bqxxe3@d0>NEl8%y0-kIZs>?ru2U7`e-Z~u@>mSV9Z#YV#nIk@!OG! zu_PpgTsC!u!g+twsJ@`nY<`yNIX#&+sAbNW)Y9|RPV0Fi1&4x^?P|oL@P7dB?#82o zFJlCV+eHGw)h^Dn+l8l(^|Qwg*cie650o3A9ZHFaeT)a-LpN@bFu^?(?u5}+3H|Gu L|3J+yLa~6~ { + }); + } + + 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 0000000000000000000000000000000000000000..2d62b0cf2ae18525c1c729b392c2d09e7d0e0362 GIT binary patch literal 7246 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 OFGWNEV4L{n?7slf$vo5m literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0e87d9445afb91486664beb2870b0615978f2e70 GIT binary patch literal 7258 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 e4#=n+G9M z1VgAS{sMo1Kfos~T71< z{B?v7B|r!{4B8I*DoDt)QYTo1oCN)%5JG7@ayQOGjUS;#mvA32$sw+oL8)UbIcU|z zj~D8w$J4wYeJEo`f~2(LAfD|M683h;kGI6}a84-b!!)*~nd6E6Iv!0MnVgY_EI(d} z*y4Syf>Js%93RFA7cs$oTD-6LtO#sMtqiq`bBSz*=A8V#cmfWe4;(K9`S;!z zN;CRFcp(nd%mL8%;)(hMKF$+4p>#YN8;guY#WSq(3{e>ObG#VD@z!+={L7#xeDRE# za17gdY?*zjA~?{^}b$S3x%g3spDU5_7y*aWCo zFc($v27UIr>ihoi{?pLnbrLVa(eKv=?mZvxti5ena|R)%f-=nZR`?-kWj3oN@LstD zeIZaGZD8R15DRMwwzx1kX9 z<*EU=Ra{4ChU&vesq^efl(8?ciT{Bp?r)oX((7H)IlQB{;-7y!8I4mqiE%T1yq_ZjL#CP*EpeWuJ-@kFad<9z6 zB3}!ljqmSmFfznOgTr6(nYG79n2+6~ONJx^k^#wpWI!??8ITM}1|$QL0m*=5Kr$d1 z_)i(=><&$0>Bqxxe3@d0>NEl8%y0-kIZs>?ru2U7`e-Z~u@>mSV9Z#YV#nIk@!OG! zu_PpgTsC!u!g+twsJ@`nY<`yNIX#&+sAbNW)Y9|RPV0Fi1&4x^?P|oL@P7dB?#82o zFJlCV+eHGw)h^Dn+l8l(^|Qwg*cie650o3A9ZHFaeT)a-LpN@bFu^?(?u5}+3H|Gu L|3J+yLa~6~ +/* 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 0000000000000000000000000000000000000000..3a71bf295302b82d0304240a162c99b4a68be15e GIT binary patch literal 1012 zcmbVLO=}ZT6n#@`Tl{FVP^{u&7B1WK zs;xAM;!Q~s{mW;0E=j#F`XnqkZ2%9x=GJylO{pF4L~)Ji(fyIK+Lz#t_;=vrGYhRg zktt>HnULy9uw{zOQGK7K(drxupPfGqYP)B@2+RZdanGJoiUH%_;(S+hyoG;;81rfk z7&Y!?{Ew~c?3>>E?&|E-fzH}P-GhH~a}`_z=F^!s&^=)9qx<|ry+7XHKG3~o-Fv@u z65Ty-2&RF~e(LNK&mDd(CaXcCVQPMmMjcZzt+X6Rt0oPbwIiah7xCE5T#&w{W9sLm z?!0`goNmb}otd8LgXIeHmx1}i!h~O`yR)U&6Zg(e$sG6b#c-uhzBAUxT%f&n0Ul?5 z-Yw#s2Kgx#KF5pLZTLB7S6Tgv%0VZfX0k~Eu4QN_j_MJ`K|-xCNKCB}y`cPn{1rT{ h(Sss->fb8+D_-288;ic#d6GYr^~wWp?O_$)`2&;%m+}Ar literal 0 HcmV?d00001 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)); + } +}