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

briansolo1985 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new c05cf69585 NIFI-14364 MiNiFi Nar auto unload in case of the extension 
is removed from the nar auto load directory
c05cf69585 is described below

commit c05cf69585de8292d0f19078a7779e937009c8ba
Author: Peter Kedvessy <[email protected]>
AuthorDate: Thu Mar 6 09:42:30 2025 +0100

    NIFI-14364 MiNiFi Nar auto unload in case of the extension is removed from 
the nar auto load directory
    
    This closes #9800.
    
    Signed-off-by: Ferenc Kis <[email protected]>
---
 .../main/assembly/dependencies-windows-service.xml |  3 +
 .../src/main/assembly/dependencies.xml             |  3 +
 .../minifi-framework/minifi-framework-core/pom.xml |  2 +-
 .../minifi-framework/minifi-nar-unloader/pom.xml   | 66 +++++++++++++++
 .../nifi/minifi/nar/NarAutoUnloadService.java      | 64 +++++++++++++++
 .../apache/nifi/minifi/nar/NarAutoUnloader.java    | 58 +++++++++++++
 .../nifi/minifi/nar/NarAutoUnloaderTask.java       | 95 ++++++++++++++++++++++
 .../minifi/nar/NarAutoUnloaderTaskFactory.java     | 55 +++++++++++++
 .../nifi/minifi/nar/NarAutoUnloadServiceTest.java  | 93 +++++++++++++++++++++
 .../minifi-framework/minifi-runtime/pom.xml        |  6 +-
 .../minifi-framework/minifi-server/pom.xml         | 12 +++
 .../apache/nifi/minifi/StandardMiNiFiServer.java   | 39 +++++++++
 .../minifi-framework/pom.xml                       |  1 +
 13 files changed, 495 insertions(+), 2 deletions(-)

diff --git 
a/minifi/minifi-assembly/src/main/assembly/dependencies-windows-service.xml 
b/minifi/minifi-assembly/src/main/assembly/dependencies-windows-service.xml
index 02068b4e41..ed6a5a0473 100644
--- a/minifi/minifi-assembly/src/main/assembly/dependencies-windows-service.xml
+++ b/minifi/minifi-assembly/src/main/assembly/dependencies-windows-service.xml
@@ -50,10 +50,13 @@
                 <include>org.apache.nifi:nifi-server-api</include>
                 <include>org.apache.nifi:nifi-stateless-api</include>
                 <include>org.apache.nifi:*:nar</include>
+                
<include>org.apache.nifi:nifi-framework-nar-loading-utils</include>
+                <include>org.apache.nifi:nifi-framework-nar-utils</include>
                 <!-- Apache NiFi MiNiFi -->
                 <include>org.apache.nifi.minifi:minifi-commons-utils</include>
                 <include>org.apache.nifi.minifi:minifi-framework-api</include>
                 <include>org.apache.nifi.minifi:minifi-runtime</include>
+                <include>org.apache.nifi.minifi:minifi-nar-unloader</include>
                 <include>org.apache.nifi.minifi:*:nar</include>
                 <include>org.apache.nifi:nifi-mqtt:nar</include>
                 <!-- Unfortunately minifi-runtime depends on jackson databind 
and annotations
diff --git a/minifi/minifi-assembly/src/main/assembly/dependencies.xml 
b/minifi/minifi-assembly/src/main/assembly/dependencies.xml
index b7e0296181..38e1aa61d2 100644
--- a/minifi/minifi-assembly/src/main/assembly/dependencies.xml
+++ b/minifi/minifi-assembly/src/main/assembly/dependencies.xml
@@ -50,11 +50,14 @@
                 <include>org.apache.nifi:nifi-runtime</include>
                 <include>org.apache.nifi:nifi-server-api</include>
                 <include>org.apache.nifi:nifi-stateless-api</include>
+                
<include>org.apache.nifi:nifi-framework-nar-loading-utils</include>
+                <include>org.apache.nifi:nifi-framework-nar-utils</include>
                 <include>org.apache.nifi:*:nar</include>
                 <!-- Apache NiFi MiNiFi -->
                 <include>org.apache.nifi.minifi:minifi-commons-utils</include>
                 <include>org.apache.nifi.minifi:minifi-framework-api</include>
                 <include>org.apache.nifi.minifi:minifi-runtime</include>
+                <include>org.apache.nifi.minifi:minifi-nar-unloader</include>
                 <include>org.apache.nifi.minifi:*:nar</include>
                 <include>org.apache.nifi:nifi-mqtt:nar</include>
                 <!-- Unfortunately minifi-runtime depends on jackson databind 
and annotations
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/pom.xml
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/pom.xml
index 1312b64cb5..673821c593 100644
--- 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/pom.xml
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/pom.xml
@@ -156,4 +156,4 @@ limitations under the License.
             <scope>test</scope>
         </dependency>
     </dependencies>
-</project>
+</project>
\ No newline at end of file
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/pom.xml
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/pom.xml
new file mode 100644
index 0000000000..b9d1c973e8
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi.minifi</groupId>
+        <artifactId>minifi-framework</artifactId>
+        <version>2.4.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>minifi-nar-unloader</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-properties</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-commons-utils</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-commons-api</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-nar-utils</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-nar-loading-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-python-framework-api</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloadService.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloadService.java
new file mode 100644
index 0000000000..6a1c56891f
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloadService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.minifi.nar;
+
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.File;
+
+public class NarAutoUnloadService {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(NarAutoUnloadService.class);
+    private static final String UNPACKED_POSTFIX = "-unpacked";
+
+    private final ExtensionManager extensionManager;
+    private final File extensionWorkDirectory;
+    private final NarLoader narLoader;
+
+    public NarAutoUnloadService(ExtensionManager extensionManager, File 
extensionWorkDirectory, NarLoader narLoader) {
+        this.extensionManager = extensionManager;
+        this.extensionWorkDirectory = extensionWorkDirectory;
+        this.narLoader = narLoader;
+    }
+
+    public void unloadNarFile(String fileName) {
+        if (isSupported(fileName)) {
+            File narWorkingDirectory = new File(extensionWorkDirectory, 
fileName + UNPACKED_POSTFIX);
+            extensionManager.getAllBundles().stream()
+                    .filter(bundle -> 
bundle.getBundleDetails().getWorkingDirectory().getPath().equals(narWorkingDirectory.getPath()))
+                    .findFirst()
+                    .ifPresentOrElse(narLoader::unload, () -> LOGGER.warn("NAR 
bundle not found for {}", fileName));
+        }
+    }
+
+    private boolean isSupported(String fileName) {
+        if (!fileName.endsWith(".nar")) {
+            LOGGER.info("Skipping non-nar file {}", fileName);
+            return false;
+        } else if (fileName.startsWith(".")) {
+            LOGGER.debug("Skipping partially written file {}", fileName);
+            return false;
+        }
+        return true;
+    }
+
+
+
+}
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloader.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloader.java
new file mode 100644
index 0000000000..70390b56e0
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloader.java
@@ -0,0 +1,58 @@
+/*
+ * 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.minifi.nar;
+
+import static java.util.Objects.requireNonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NarAutoUnloader {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(NarAutoUnloader.class);
+    private final NarAutoUnloaderTaskFactory narAutoUnloaderTaskFactory;
+
+    private NarAutoUnloaderTask narAutoUnLoaderTask;
+    private boolean started = false;
+
+    public NarAutoUnloader(NarAutoUnloaderTaskFactory 
narAutoUnloaderTaskFactory) {
+        this.narAutoUnloaderTaskFactory = 
requireNonNull(narAutoUnloaderTaskFactory);
+    }
+
+    public synchronized void start() throws Exception {
+        if (!started) {
+            narAutoUnLoaderTask = 
narAutoUnloaderTaskFactory.createNarAutoUnloaderTask();
+
+            LOGGER.info("Starting NAR Auto-Unloader Thread for directory {}", 
narAutoUnLoaderTask.getAutoLoadPath());
+
+            Thread.ofVirtual().name("NAR 
Auto-Unloader").start(narAutoUnLoaderTask);
+
+            started = true;
+        }
+    }
+
+    public synchronized void stop() {
+        started = false;
+        if (narAutoUnLoaderTask != null) {
+            narAutoUnLoaderTask.stop();
+            narAutoUnLoaderTask = null;
+        }
+
+        LOGGER.info("NAR Auto-Unloader stopped");
+    }
+}
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTask.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTask.java
new file mode 100644
index 0000000000..6d15d8f95c
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTask.java
@@ -0,0 +1,95 @@
+/*
+ * 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.minifi.nar;
+
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.util.Objects.requireNonNull;
+
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NarAutoUnloaderTask implements Runnable {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(NarAutoUnloaderTask.class);
+    private static final long POLL_INTERVAL_MS = 5000;
+
+    private final Path autoLoadPath;
+    private final WatchService watchService;
+    private final NarAutoUnloadService narAutoUnloadService;
+
+    private volatile boolean stopped = false;
+
+    public NarAutoUnloaderTask(
+            Path autoLoadPath,
+            WatchService watchService,
+            NarAutoUnloadService narAutoUnloadService) {
+        this.autoLoadPath = requireNonNull(autoLoadPath);
+        this.watchService = requireNonNull(watchService);
+        this.narAutoUnloadService = requireNonNull(narAutoUnloadService);
+    }
+
+    @Override
+    public void run() {
+        while (!stopped) {
+            try {
+                WatchKey key;
+                try {
+                    LOGGER.debug("Polling for removed NARs at {}", 
autoLoadPath);
+                    key = watchService.poll(POLL_INTERVAL_MS, 
TimeUnit.MILLISECONDS);
+                } catch (InterruptedException x) {
+                    LOGGER.info("WatchService interrupted, returning...");
+                    return;
+                }
+                if (key != null) {
+                    for (WatchEvent<?> event : key.pollEvents()) {
+                        if (event.kind() == ENTRY_DELETE) {
+                            
narAutoUnloadService.unloadNarFile(getFileName(event));
+                        }
+                    }
+                    if (!key.reset()) {
+                        LOGGER.error("NAR auto-load directory is no longer 
valid");
+                        stop();
+                    }
+                }
+            } catch (final Throwable t) {
+                LOGGER.error("Error un-loading NARs", t);
+            }
+        }
+    }
+
+    public void stop() {
+        LOGGER.info("Stopping NAR Auto-Unloader");
+        stopped = true;
+    }
+
+    public Path getAutoLoadPath() {
+        return autoLoadPath;
+    }
+
+    private String getFileName(WatchEvent<?> event) {
+        WatchEvent<Path> ev = (WatchEvent<Path>) event;
+        Path filename = ev.context();
+        Path autoUnLoadFile = autoLoadPath.resolve(filename);
+        return autoUnLoadFile.toFile().getName().toLowerCase();
+    }
+}
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTaskFactory.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTaskFactory.java
new file mode 100644
index 0000000000..4052165b91
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/main/java/org/apache/nifi/minifi/nar/NarAutoUnloaderTaskFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.minifi.nar;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchService;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarLoader;
+import org.apache.nifi.util.FileUtils;
+import org.apache.nifi.util.NiFiProperties;
+
+public class NarAutoUnloaderTaskFactory {
+
+    private final NiFiProperties properties;
+    private final ExtensionManager extensionManager;
+    private final NarLoader narLoader;
+
+    public NarAutoUnloaderTaskFactory(NiFiProperties properties, 
ExtensionManager extensionManager, NarLoader narLoader) {
+        this.properties = requireNonNull(properties);
+        this.extensionManager = requireNonNull(extensionManager);
+        this.narLoader = requireNonNull(narLoader);
+    }
+
+    public NarAutoUnloaderTask createNarAutoUnloaderTask() throws IOException {
+        File autoLoadDir = properties.getNarAutoLoadDirectory();
+        FileUtils.ensureDirectoryExistAndCanRead(autoLoadDir);
+        WatchService watcher = FileSystems.getDefault().newWatchService();
+        Path autoLoadPath = autoLoadDir.toPath();
+        autoLoadPath.register(watcher, StandardWatchEventKinds.ENTRY_DELETE);
+        NarAutoUnloadService narAutoUnloadService = new 
NarAutoUnloadService(extensionManager, 
properties.getExtensionsWorkingDirectory(), narLoader);
+
+        return new NarAutoUnloaderTask(autoLoadPath, watcher, 
narAutoUnloadService);
+    }
+}
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/test/java/org/apache/nifi/minifi/nar/NarAutoUnloadServiceTest.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/test/java/org/apache/nifi/minifi/nar/NarAutoUnloadServiceTest.java
new file mode 100644
index 0000000000..45e95166f0
--- /dev/null
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-nar-unloader/src/test/java/org/apache/nifi/minifi/nar/NarAutoUnloadServiceTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.minifi.nar;
+
+import java.io.File;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarLoader;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+
+@ExtendWith(MockitoExtension.class)
+public class NarAutoUnloadServiceTest {
+
+    private static final String SUPPORTED_FILENAME = "test.nar";
+    private static final String UNPACKED_POSTFIX = "-unpacked";
+
+    @Mock
+    private ExtensionManager extensionManager;
+    @Mock
+    private NarLoader narLoader;
+    private File extensionWorkDirectory;
+
+    private NarAutoUnloadService victim;
+
+    @BeforeEach
+    public void initTest() {
+        extensionWorkDirectory = new File(".");
+        victim = new NarAutoUnloadService(extensionManager, 
extensionWorkDirectory, narLoader);
+    }
+
+    @ParameterizedTest
+    @MethodSource("notSupportedFileNames")
+    public void testNotSupportedFileDoesNotTriggerUnload(String fileName) {
+
+        victim.unloadNarFile(fileName);
+
+        verifyNoInteractions(extensionManager, narLoader);
+    }
+
+    @Test
+    public void testUnload() {
+        Bundle bundle = mock(Bundle.class);
+        BundleDetails bundleDetails = mock(BundleDetails.class);
+        File workingDirectory = new File(extensionWorkDirectory, 
SUPPORTED_FILENAME + UNPACKED_POSTFIX);
+
+        when(extensionManager.getAllBundles()).thenReturn(Set.of(bundle));
+        when(bundle.getBundleDetails()).thenReturn(bundleDetails);
+        when(bundleDetails.getWorkingDirectory()).thenReturn(workingDirectory);
+
+        victim.unloadNarFile(SUPPORTED_FILENAME);
+
+        verify(narLoader).unload(bundle);
+    }
+
+    private static Stream<Arguments> notSupportedFileNames() {
+        return Stream.of(
+                Arguments.of("nar.jar"),
+                Arguments.of(".test.nar")
+        );
+    }
+}
\ No newline at end of file
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-runtime/pom.xml
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-runtime/pom.xml
index a2432d70aa..8a1241a949 100644
--- 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-runtime/pom.xml
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-runtime/pom.xml
@@ -45,6 +45,11 @@ limitations under the License.
             <artifactId>minifi-flow-status-report</artifactId>
             <version>2.4.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-nar-unloader</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-nar-utils</artifactId>
@@ -82,6 +87,5 @@ limitations under the License.
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
         </dependency>
-
     </dependencies>
 </project>
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/pom.xml
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/pom.xml
index ddb0d16e61..3fe3b3a07c 100644
--- 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/pom.xml
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/pom.xml
@@ -51,6 +51,18 @@
             <artifactId>nifi-framework-api</artifactId>
             <version>2.4.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-nar-utils</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-nar-loading-utils</artifactId>
+            <version>2.4.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-properties</artifactId>
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/src/main/java/org/apache/nifi/minifi/StandardMiNiFiServer.java
 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/src/main/java/org/apache/nifi/minifi/StandardMiNiFiServer.java
index b922105d46..396931bb14 100644
--- 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/src/main/java/org/apache/nifi/minifi/StandardMiNiFiServer.java
+++ 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-server/src/main/java/org/apache/nifi/minifi/StandardMiNiFiServer.java
@@ -20,11 +20,14 @@ package org.apache.nifi.minifi;
 import static java.util.Optional.ofNullable;
 import static org.apache.commons.lang3.StringUtils.EMPTY;
 import static org.apache.nifi.minifi.validator.FlowValidator.validate;
+import static org.apache.nifi.nar.NarUnpackMode.UNPACK_INDIVIDUAL_JARS;
+import static org.apache.nifi.nar.NarUnpackMode.UNPACK_TO_UBER_JAR;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.List;
 import java.util.Optional;
+
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.headless.HeadlessNiFiServer;
@@ -32,8 +35,18 @@ import org.apache.nifi.minifi.bootstrap.BootstrapListener;
 import org.apache.nifi.minifi.c2.C2NifiClientService;
 import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
 import org.apache.nifi.minifi.commons.status.FlowStatusReport;
+import org.apache.nifi.minifi.nar.NarAutoUnloader;
+import org.apache.nifi.minifi.nar.NarAutoUnloaderTaskFactory;
 import org.apache.nifi.minifi.status.StatusConfigReporter;
 import org.apache.nifi.minifi.status.StatusRequestException;
+import org.apache.nifi.nar.ExtensionDiscoveringManager;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.ExtensionManagerHolder;
+import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.nar.NarClassLoadersHolder;
+import org.apache.nifi.nar.NarLoader;
+import org.apache.nifi.nar.NarUnpackMode;
+import org.apache.nifi.nar.StandardNarLoader;
 import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,6 +59,7 @@ public class StandardMiNiFiServer extends HeadlessNiFiServer 
implements MiNiFiSe
 
     private BootstrapListener bootstrapListener;
     private C2NifiClientService c2NifiClientService;
+    private NarAutoUnloader narAutoUnloader;
 
     @Override
     public void start() {
@@ -55,6 +69,7 @@ public class StandardMiNiFiServer extends HeadlessNiFiServer 
implements MiNiFiSe
         initC2();
         sendStartedStatus();
         startHeartbeat();
+        startNarAutoUnloader();
     }
 
     @Override
@@ -84,6 +99,9 @@ public class StandardMiNiFiServer extends HeadlessNiFiServer 
implements MiNiFiSe
         if (c2NifiClientService != null) {
             c2NifiClientService.stop();
         }
+        if (narAutoUnloader != null) {
+            narAutoUnloader.stop();
+        }
     }
 
     public FlowStatusReport getStatusReport(String requestString) throws 
StatusRequestException {
@@ -157,4 +175,25 @@ public class StandardMiNiFiServer extends 
HeadlessNiFiServer implements MiNiFiSe
             }
         }
     }
+
+    private void startNarAutoUnloader() {
+        try {
+            NiFiProperties properties = getNiFiProperties();
+            ExtensionManager extensionManager = 
ExtensionManagerHolder.getExtensionManager();
+            NarUnpackMode unpackMode = properties.isUnpackNarsToUberJar() ? 
UNPACK_TO_UBER_JAR : UNPACK_INDIVIDUAL_JARS;
+            NarLoader narLoader = new StandardNarLoader(
+                    properties.getExtensionsWorkingDirectory(),
+                    NarClassLoadersHolder.getInstance(),
+                    (ExtensionDiscoveringManager) extensionManager,
+                    new ExtensionMapping(),
+                    null,
+                    unpackMode);
+
+            NarAutoUnloaderTaskFactory narAutoUnLoaderTaskFactory = new 
NarAutoUnloaderTaskFactory(properties, extensionManager, narLoader);
+            narAutoUnloader = new NarAutoUnloader(narAutoUnLoaderTaskFactory);
+            narAutoUnloader.start();
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 }
diff --git 
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/pom.xml 
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/pom.xml
index d11d3ec96e..af29968f67 100644
--- a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/pom.xml
+++ b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/pom.xml
@@ -31,5 +31,6 @@ limitations under the License.
         <module>minifi-resources</module>
         <module>minifi-server</module>
         <module>minifi-properties-loader</module>
+        <module>minifi-nar-unloader</module>
     </modules>
 </project>
\ No newline at end of file

Reply via email to