From 44abe3998317e8f6ecd49f42a80c53addb97d8ad Mon Sep 17 00:00:00 2001 From: Bryan Bende Date: Tue, 17 Sep 2024 22:41:45 -0400 Subject: [PATCH] NIFI-13757 Improve handling when a NAR contains an extension that produces an Error (#9274) Signed-off-by: David Handermann --- .../nifi/documentation/DocGenerator.java | 10 +++-- .../org/apache/nifi/nar/NarInstallTask.java | 13 +++++-- .../apache/nifi/web/api/dto/DtoFactory.java | 37 +++++++++++-------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java b/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java index bebc7415f4..71735417aa 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java +++ b/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java @@ -114,13 +114,15 @@ public class DocGenerator { } } case JAVA -> { - final Class extensionClass = extensionManager.getClass(extensionDefinition); - final Class componentClass = extensionClass.asSubclass(ConfigurableComponent.class); + // Catch Throwable here to protect against an extension that may have been registered, but throws an Error when attempting to access the Class, + // this method is called during start-up, and we don't want to fail starting just because one bad extension class can't be loaded try { + final Class extensionClass = extensionManager.getClass(extensionDefinition); + final Class componentClass = extensionClass.asSubclass(ConfigurableComponent.class); logger.debug("Documentation generation started: Component Class [{}]", componentClass); document(extensionManager, componentDirectory, componentClass, coordinate); - } catch (Exception e) { - logger.warn("Documentation generation failed: Component Class [{}]", componentClass, e); + } catch (Throwable t) { + logger.warn("Documentation generation failed: Component Class [{}]", extensionDefinition.getImplementationClassName(), t); } } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java index 73266c2588..5eee3f3dff 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java @@ -99,6 +99,13 @@ public class NarInstallTask implements Runnable { final BundleCoordinate loadedCoordinate = loadedBundle.getBundleDetails().getCoordinate(); LOGGER.info("NAR [{}] was installed", loadedCoordinate); if (loadedCoordinate.equals(coordinate)) { + // If the NAR that was just uploaded was successfully loaded, attempt to access the class of each extension to prove that each + // class can load successfully, if not then we want to bounce out to the catch block and set the state as FAILED + final Set loadedExtensionDefinitions = extensionManager.getTypes(coordinate); + for (final ExtensionDefinition loadedExtensionDefinition : loadedExtensionDefinitions) { + final Class extensionClass = extensionManager.getClass(loadedExtensionDefinition); + LOGGER.debug("Loaded [{}] from bundle [{}]", extensionClass.getCanonicalName(), coordinate); + } narNode.setState(NarState.INSTALLED); } else { try { @@ -133,10 +140,10 @@ public class NarInstallTask implements Runnable { // Notify the NAR Manager that the install task complete for the current NAR narManager.completeInstall(narNode.getIdentifier()); - } catch (final Exception e) { - LOGGER.error("Failed to install NAR [{}]", coordinate, e); + } catch (final Throwable t) { + LOGGER.error("Failed to install NAR [{}]", coordinate, t); narNode.setState(NarState.FAILED); - narNode.setFailureMessage(e.getMessage()); + narNode.setFailureMessage(t.getMessage()); } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 189e0f9de8..dede174513 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -257,6 +257,8 @@ import org.apache.nifi.web.controller.ControllerFacade; import org.apache.nifi.web.revision.RevisionManager; import jakarta.ws.rs.WebApplicationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.text.Collator; @@ -288,20 +290,17 @@ import java.util.stream.Collectors; public final class DtoFactory { - @SuppressWarnings("rawtypes") - private final static Comparator CLASS_NAME_COMPARATOR = new Comparator() { - @Override - public int compare(final Class class1, final Class class2) { - return Collator.getInstance(Locale.US).compare(class1.getSimpleName(), class2.getSimpleName()); - } - }; - public static final String SENSITIVE_VALUE_MASK = "********"; + private static final Logger logger = LoggerFactory.getLogger(DtoFactory.class); - private BulletinRepository bulletinRepository; - private ControllerServiceProvider controllerServiceProvider; - private EntityFactory entityFactory; - private Authorizer authorizer; - private ExtensionManager extensionManager; + @SuppressWarnings("rawtypes") + private final static Comparator CLASS_NAME_COMPARATOR = (class1, class2) -> Collator.getInstance(Locale.US).compare(class1.getSimpleName(), class2.getSimpleName()); + public static final String SENSITIVE_VALUE_MASK = "********"; + + private BulletinRepository bulletinRepository; + private ControllerServiceProvider controllerServiceProvider; + private EntityFactory entityFactory; + private Authorizer authorizer; + private ExtensionManager extensionManager; public ControllerConfigurationDTO createControllerConfigurationDto(final ControllerFacade controllerFacade) { final ControllerConfigurationDTO dto = new ControllerConfigurationDTO(); @@ -3291,9 +3290,15 @@ public final class DtoFactory { continue; } - final Class cls = extensionManager.getClass(extensionDefinition); - if (cls != null) { - classBundles.put(cls, extensionDefinition.getBundle()); + // Catch Throwable here to protect against an extension that may have been registered, but throws an Error when attempting to access the Class, + // we don't need to fail the overall request since we can still return all the other components that are still usable + try { + final Class cls = extensionManager.getClass(extensionDefinition); + if (cls != null) { + classBundles.put(cls, extensionDefinition.getBundle()); + } + } catch (final Throwable t) { + logger.warn("Unable to get extension class for [{}]", extensionDefinition.getImplementationClassName(), t); } }