This is an automated email from the ASF dual-hosted git repository.
mcgilman pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/NIFI-15258 by this push:
new 73a0dec49c NIFI-15430: Ensure that if we fail to initialize a
Connector, we crea… (#10733)
73a0dec49c is described below
commit 73a0dec49cb8a8bc597b562a53de3b8ce102903f
Author: Mark Payne <[email protected]>
AuthorDate: Wed Jan 7 16:49:07 2026 -0500
NIFI-15430: Ensure that if we fail to initialize a Connector, we crea…
(#10733)
* NIFI-15430: Ensure that if we fail to initialize a Connector, we create a
GhostConnector instead and ensure that we also proivde the extensionMissing
flag on ConnectorNode
* NIFI-15430: Added extensionMissing flag to Connector DTO
* NIFI-15430: If unable to load initial flow of a Connector, make ghosted
connector instead
---
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 10 +++
.../nifi/components/connector/ConnectorNode.java | 6 ++
.../connector/StandardConnectorNode.java | 9 ++-
.../apache/nifi/controller/ExtensionBuilder.java | 94 ++++++++++++++--------
.../nifi/controller/flow/StandardFlowManager.java | 11 +--
.../connector/TestStandardConnectorNode.java | 3 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 19 +++--
7 files changed, 98 insertions(+), 54 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
index 96849a58a0..a2253fdfeb 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -37,6 +37,7 @@ public class ConnectorDTO extends ComponentDTO {
private Collection<String> validationErrors;
private String validationStatus;
private Boolean multipleVersionsAvailable;
+ private Boolean extensionMissing;
private String configurationUrl;
private String detailsUrl;
@@ -148,4 +149,13 @@ public class ConnectorDTO extends ComponentDTO {
public void setDetailsUrl(final String detailsUrl) {
this.detailsUrl = detailsUrl;
}
+
+ @Schema(description = "Whether the extension for this connector is
missing.")
+ public Boolean getExtensionMissing() {
+ return extensionMissing;
+ }
+
+ public void setExtensionMissing(final Boolean extensionMissing) {
+ this.extensionMissing = extensionMissing;
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index 9dd1fe36a1..b7206edbd3 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -66,6 +66,12 @@ public interface ConnectorNode extends
ComponentAuthorizable, VersionedComponent
BundleCoordinate getBundleCoordinate();
+ /**
+ * Returns whether or not the underlying extension is missing (i.e., the
Connector is a GhostConnector).
+ * @return true if the extension is missing, false otherwise
+ */
+ boolean isExtensionMissing();
+
List<AllowableValue> fetchAllowableValues(String stepName, String
propertyName);
List<AllowableValue> fetchAllowableValues(String stepName, String
propertyName, String filter);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 59f2dfba10..14713ecee9 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -89,6 +89,7 @@ public class StandardConnectorNode implements ConnectorNode {
private final AtomicReference<ValidationState> validationState = new
AtomicReference<>(new ValidationState(ValidationStatus.VALIDATING,
Collections.emptyList()));
private final ConnectorValidationTrigger validationTrigger;
+ private final boolean extensionMissing;
private volatile boolean triggerValidation = true;
private volatile FrameworkFlowContext workingFlowContext;
@@ -101,7 +102,7 @@ public class StandardConnectorNode implements ConnectorNode
{
final Authorizable parentAuthorizable, final ConnectorDetails
connectorDetails, final String componentType, final String
componentCanonicalClass,
final MutableConnectorConfigurationContext configurationContext,
final ConnectorStateTransition stateTransition, final
FlowContextFactory flowContextFactory,
- final ConnectorValidationTrigger validationTrigger) {
+ final ConnectorValidationTrigger validationTrigger, final boolean
extensionMissing) {
this.identifier = identifier;
this.flowManager = flowManager;
@@ -114,6 +115,7 @@ public class StandardConnectorNode implements ConnectorNode
{
this.stateTransition = stateTransition;
this.flowContextFactory = flowContextFactory;
this.validationTrigger = validationTrigger;
+ this.extensionMissing = extensionMissing;
this.name = connectorDetails.getConnector().getClass().getSimpleName();
@@ -531,6 +533,11 @@ public class StandardConnectorNode implements
ConnectorNode {
return bundleCoordinate;
}
+ @Override
+ public boolean isExtensionMissing() {
+ return extensionMissing;
+ }
+
@Override
public List<AllowableValue> fetchAllowableValues(final String stepName,
final String propertyName) {
if (workingFlowContext == null) {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 64e4af6c72..3f16f8d25e 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -19,11 +19,11 @@ package org.apache.nifi.controller;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
+import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
-import org.apache.nifi.annotation.behavior.SupportsBatching;
-import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
@@ -33,9 +33,9 @@ import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorDetails;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
-import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorStateTransition;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowContextFactory;
import
org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
@@ -476,37 +476,13 @@ public class ExtensionBuilder {
return flowAnalysisRuleNode;
}
- public ConnectorNode buildConnector() {
+ public ConnectorNode buildConnector(final boolean loadInitialFlow) {
requireNonNull(identifier, "Connector ID");
requireNonNull(type, "Connector Type");
requireNonNull(bundleCoordinate, "Bundle Coordinate");
requireNonNull(extensionManager, "Extension Manager");
requireNonNull(managedProcessGroup, "Managed Process Group");
- boolean creationSuccessful = true;
- Connector connector;
-
- try {
- connector = createConnector();
- } catch (final Exception e) {
- logger.error("Could not create Connector of type {} from {} for ID
{} due to: {}; creating \"Ghost\" implementation", type, bundleCoordinate,
identifier, e.getMessage(), e);
- connector = new GhostConnector(identifier, type);
- creationSuccessful = false;
- }
-
- final String componentType;
- if (creationSuccessful) {
- componentType = connector.getClass().getSimpleName();
- } else {
- final String simpleClassName = type.contains(".") ?
StringUtils.substringAfterLast(type, ".") : type;
- componentType = "(Missing) " + simpleClassName;
- }
-
- final ComponentLog logger = new SimpleProcessLogger(identifier,
connector, new StandardLoggingContext());
- final ConnectorDetails connectorDetails = new
ConnectorDetails(connector, bundleCoordinate, logger);
-
- final FrameworkConnectorInitializationContext initContext =
createConnectorInitializationContext(managedProcessGroup, logger);
-
final Authorizable connectorsAuthorizable = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
@@ -519,6 +495,18 @@ public class ExtensionBuilder {
}
};
+ final Connector connector;
+ try {
+ connector = createConnector();
+ } catch (final Exception e) {
+ logger.error("Could not create Connector of type {} from {} for ID
{} due to: {}; creating \"Ghost\" implementation", type, bundleCoordinate,
identifier, e.getMessage(), e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+
+ final String componentType = connector.getClass().getSimpleName();
+ final ComponentLog componentLog = new SimpleProcessLogger(identifier,
connector, new StandardLoggingContext());
+ final ConnectorDetails connectorDetails = new
ConnectorDetails(connector, bundleCoordinate, componentLog);
+
final ConnectorNode connectorNode = new StandardConnectorNode(
identifier,
flowController.getFlowManager(),
@@ -530,16 +518,58 @@ public class ExtensionBuilder {
activeConfigurationContext,
connectorStateTransition,
flowContextFactory,
- connectorValidationTrigger
+ connectorValidationTrigger,
+ false
);
- initializeDefaultValues(connector,
connectorNode.getActiveFlowContext());
- // TODO: If an Exception is thrown in the call to #initialize, we
should create a Ghosted Connector
- connectorNode.initializeConnector(initContext);
+ try {
+ initializeDefaultValues(connector,
connectorNode.getActiveFlowContext());
+
+ final FrameworkConnectorInitializationContext initContext =
createConnectorInitializationContext(managedProcessGroup, componentLog);
+ connectorNode.initializeConnector(initContext);
+ } catch (final Exception e) {
+ logger.error("Could not initialize Connector of type {} from {} for
ID {}; creating \"Ghost\" implementation", type, bundleCoordinate, identifier,
e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+
+ if (loadInitialFlow) {
+ try {
+ connectorNode.loadInitialFlow();
+ } catch (final Exception e) {
+ logger.error("Failed to load initial flow for {}; creating
\"Ghost\" implementation", connectorNode, e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+ }
return connectorNode;
}
+ private ConnectorNode createGhostConnectorNode(final Authorizable
connectorsAuthorizable) {
+ final GhostConnector ghostConnector = new GhostConnector(identifier,
type);
+ final String simpleClassName = type.contains(".") ?
StringUtils.substringAfterLast(type, ".") : type;
+ final String componentType = "(Missing) " + simpleClassName;
+ final ComponentLog componentLog = new SimpleProcessLogger(identifier,
ghostConnector, new StandardLoggingContext());
+ final ConnectorDetails connectorDetails = new
ConnectorDetails(ghostConnector, bundleCoordinate, componentLog);
+
+ // If an instance class loader has been created for this connector,
remove it because it's no longer necessary.
+ extensionManager.removeInstanceClassLoader(identifier);
+
+ return new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ type,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory,
+ connectorValidationTrigger,
+ true
+ );
+ }
+
private void initializeDefaultValues(final Connector connector, final
FrameworkFlowContext flowContext) {
try (final NarCloseable ignored =
NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(),
identifier)) {
final List<ConfigurationStep> configSteps =
connector.getConfigurationSteps();
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 65e8011b90..96bbd8f4f7 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -779,16 +779,7 @@ public class StandardFlowManager extends
AbstractFlowManager implements FlowMana
.connectorStateTransition(stateTransition)
.connectorInitializationContextBuilder(flowController.getConnectorRepository().createInitializationContextBuilder())
.connectorValidationTrigger(flowController.getConnectorValidationTrigger())
- .buildConnector();
-
- if (firstTimeAdded) {
- try {
- connectorNode.loadInitialFlow();
- } catch (final Exception e) {
- logger.error("Failed to load initial flow for Connector {}",
connectorNode, e);
- // TODO: Create a Ghosted Connector instead
- }
- }
+ .buildConnector(firstTimeAdded);
// Establish the Connector as the parent authorizable of the managed
root group
managedRootGroup.setExplicitParentAuthorizable(connectorNode);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 03da830e03..a1490409da 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -513,7 +513,8 @@ public class TestStandardConnectorNode {
new StandardConnectorConfigurationContext(assetManager,
secretsManager),
stateTransition,
flowContextFactory,
- validationTrigger);
+ validationTrigger,
+ false);
// mock secrets manager
final SecretsManager secretsManager = mock(SecretsManager.class);
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 9ecb1e78d2..cf889dfc2c 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
@@ -66,14 +66,9 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDependency;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
-import org.apache.nifi.components.connector.ConnectorAssetRepository;
-import org.apache.nifi.components.state.Scope;
-import org.apache.nifi.components.state.StateMap;
-import org.apache.nifi.components.validation.ValidationState;
-import org.apache.nifi.components.validation.ValidationStatus;
-import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
@@ -85,6 +80,11 @@ import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateMap;
+import org.apache.nifi.components.validation.ValidationState;
+import org.apache.nifi.components.validation.ValidationStatus;
+import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Funnel;
@@ -144,8 +144,8 @@ import org.apache.nifi.groups.ProcessGroupCounts;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroupCounts;
import org.apache.nifi.history.History;
-import org.apache.nifi.nar.ExtensionDefinition;
import org.apache.nifi.manifest.RuntimeManifestService;
+import org.apache.nifi.nar.ExtensionDefinition;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarManifest;
@@ -5256,9 +5256,8 @@ public final class DtoFactory {
final ValidationState validationState = connector.getValidationState();
dto.setValidationStatus(validationState.getStatus().name());
dto.setValidationErrors(convertValidationErrors(validationState.getValidationErrors()));
-
- final String canonicalName =
connector.getConnector().getClass().getCanonicalName();
- dto.setType(canonicalName != null ? canonicalName :
connector.getConnector().getClass().getName());
+ dto.setType(connector.getCanonicalClassName());
+ dto.setExtensionMissing(connector.isExtensionMissing());
dto.setBundle(createBundleDto(connector.getBundleCoordinate()));
dto.setState(connector.getCurrentState().name());