This is an automated email from the ASF dual-hosted git repository. markap14 pushed a commit to branch NIFI-15258 in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 7a288a57bfc522f2b68562d2052dee44ccab324c Author: Mark Payne <[email protected]> AuthorDate: Wed Jan 21 12:25:58 2026 -0500 NIFI-15451: Added ability for Connectors to retrieve bundles availabl… (#10756) * NIFI-15451: Added ability for Connectors to retrieve bundles available for component types and updated VersionedFlowUtils to make use of it to easily update Versioned components * NIFI-15451: Added additional unit tests to VersionedFlowUtils; updates to how we compare component versions * NIFI-15451: Fixed PMD violation --- .../connector/util/VersionedFlowUtils.java | 212 ++++++++++++ .../connector/util/TestVersionedFlowUtils.java | 368 +++++++++++++++++++++ .../server/MockConnectorInitializationContext.java | 19 +- ...eworkConnectorInitializationContextBuilder.java | 2 + .../connector/StandardComponentBundleLookup.java | 45 +++ .../StandardConnectorInitializationContext.java | 15 +- .../apache/nifi/controller/ExtensionBuilder.java | 11 +- .../nifi/controller/flow/StandardFlowManager.java | 4 + .../tests/system/CalculateConnector.java | 3 - 9 files changed, 672 insertions(+), 7 deletions(-) diff --git a/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java index 8eb839a3ba..62c42e107b 100644 --- a/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java +++ b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java @@ -19,12 +19,14 @@ package org.apache.nifi.components.connector.util; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.nifi.components.connector.ComponentBundleLookup; import org.apache.nifi.flow.Bundle; import org.apache.nifi.flow.ComponentType; import org.apache.nifi.flow.ConnectableComponent; import org.apache.nifi.flow.ConnectableComponentType; import org.apache.nifi.flow.Position; import org.apache.nifi.flow.ScheduledState; +import org.apache.nifi.flow.VersionedConfigurableExtension; import org.apache.nifi.flow.VersionedConnection; import org.apache.nifi.flow.VersionedControllerService; import org.apache.nifi.flow.VersionedExternalFlow; @@ -363,4 +365,214 @@ public class VersionedFlowUtils { removeUnreferencedControllerServices(childGroup, referencedServiceIds); } } + + /** + * Updates all processors and controller services in the given process group (and its child groups) + * to use the latest available bundle version. See {@link #getLatest(List)} for details on how + * version comparison is performed. + * + * @param processGroup the process group containing components to update + * @param componentBundleLookup the lookup used to find available bundles for each component type + */ + public static void updateToLatestBundles(final VersionedProcessGroup processGroup, final ComponentBundleLookup componentBundleLookup) { + for (final VersionedProcessor processor : processGroup.getProcessors()) { + updateToLatestBundle(processor, componentBundleLookup); + } + + for (final VersionedControllerService service : processGroup.getControllerServices()) { + updateToLatestBundle(service, componentBundleLookup); + } + + for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) { + updateToLatestBundles(childGroup, componentBundleLookup); + } + } + + /** + * Updates the given processor to use the latest available bundle version. + * See {@link #getLatest(List)} for details on how version comparison is performed. + * + * @param processor the processor to update + * @param componentBundleLookup the lookup used to find available bundles for the processor type + */ + public static void updateToLatestBundle(final VersionedProcessor processor, final ComponentBundleLookup componentBundleLookup) { + final Bundle latest = getLatestBundle(processor, componentBundleLookup); + processor.setBundle(latest); + } + + /** + * Updates the given controller service to use the latest available bundle version. + * See {@link #getLatest(List)} for details on how version comparison is performed. + * + * @param service the controller service to update + * @param componentBundleLookup the lookup used to find available bundles for the service type + */ + public static void updateToLatestBundle(final VersionedControllerService service, final ComponentBundleLookup componentBundleLookup) { + final Bundle latest = getLatestBundle(service, componentBundleLookup); + service.setBundle(latest); + } + + private static Bundle getLatestBundle(final VersionedConfigurableExtension versionedComponent, final ComponentBundleLookup componentBundleLookup) { + final String type = versionedComponent.getType(); + final List<Bundle> bundles = componentBundleLookup.getAvailableBundles(type); + final List<Bundle> includingExisting = new ArrayList<>(bundles); + if (versionedComponent.getBundle() != null && !includingExisting.contains(versionedComponent.getBundle())) { + includingExisting.add(versionedComponent.getBundle()); + } + + return getLatest(includingExisting); + } + + /** + * Returns the bundle with the latest version from the given list. + * + * <p>Version comparison follows these rules:</p> + * <ol> + * <li>Versions are split by dots (e.g., "2.1.0" becomes ["2", "1", "0"]). + * This also supports calendar-date formats (e.g., "2026.01.01").</li> + * <li>Each part is compared numerically when possible; numeric parts are considered + * newer than non-numeric parts (e.g., "2.0.0" > "2.0.next")</li> + * <li>When base versions are equal, qualifiers are compared with the following precedence + * (highest to lowest): + * <ul> + * <li>Release (no qualifier)</li> + * <li>RC (Release Candidate) - e.g., "-RC1", "-RC2"</li> + * <li>M (Milestone) - e.g., "-M1", "-M2"</li> + * <li>Other/unknown qualifiers</li> + * <li>SNAPSHOT</li> + * </ul> + * </li> + * <li>Within the same qualifier type, numeric suffixes are compared + * (e.g., "2.0.0-RC2" > "2.0.0-RC1", "2.0.0-M4" > "2.0.0-M1")</li> + * </ol> + * + * <p>Examples of version ordering (highest to lowest):</p> + * <ul> + * <li>2.0.0 > 2.0.0-RC2 > 2.0.0-RC1 > 2.0.0-M4 > 2.0.0-M1 > 2.0.0-SNAPSHOT</li> + * <li>2.1.0-SNAPSHOT > 2.0.0 (higher base version wins)</li> + * <li>2026.01.01 > 2025.12.31 (calendar-date format)</li> + * </ul> + * + * @param bundles the list of bundles to compare + * @return the bundle with the latest version, or null if the list is empty + */ + public static Bundle getLatest(final List<Bundle> bundles) { + Bundle latest = null; + for (final Bundle bundle : bundles) { + if (latest == null || compareVersion(bundle.getVersion(), latest.getVersion()) > 0) { + latest = bundle; + } + } + + return latest; + } + + private static int compareVersion(final String v1, final String v2) { + final String baseVersion1 = getBaseVersion(v1); + final String baseVersion2 = getBaseVersion(v2); + + final String[] parts1 = baseVersion1.split("\\."); + final String[] parts2 = baseVersion2.split("\\."); + + final int length = Math.max(parts1.length, parts2.length); + for (int i = 0; i < length; i++) { + final String part1Str = i < parts1.length ? parts1[i] : "0"; + final String part2Str = i < parts2.length ? parts2[i] : "0"; + + final int comparison = compareVersionPart(part1Str, part2Str); + if (comparison != 0) { + return comparison; + } + } + + // Base versions are equal; compare qualifiers + final String qualifier1 = getQualifier(v1); + final String qualifier2 = getQualifier(v2); + return compareQualifiers(qualifier1, qualifier2); + } + + private static int compareQualifiers(final String qualifier1, final String qualifier2) { + final int rank1 = getQualifierRank(qualifier1); + final int rank2 = getQualifierRank(qualifier2); + + if (rank1 != rank2) { + return Integer.compare(rank1, rank2); + } + + // Same qualifier type; compare numeric suffixes (e.g., RC2 > RC1, M4 > M3) + final int num1 = getQualifierNumber(qualifier1); + final int num2 = getQualifierNumber(qualifier2); + return Integer.compare(num1, num2); + } + + private static int getQualifierRank(final String qualifier) { + if (qualifier == null || qualifier.isEmpty()) { + return 4; + } else if (qualifier.startsWith("RC")) { + return 3; + } else if (qualifier.startsWith("M")) { + return 2; + } else if (qualifier.equals("SNAPSHOT")) { + return 0; + } else { + return 1; + } + } + + private static int getQualifierNumber(final String qualifier) { + if (qualifier == null || qualifier.isEmpty()) { + return 0; + } + + final StringBuilder digits = new StringBuilder(); + for (int i = 0; i < qualifier.length(); i++) { + final char c = qualifier.charAt(i); + if (Character.isDigit(c)) { + digits.append(c); + } + } + + if (digits.isEmpty()) { + return 0; + } + + try { + return Integer.parseInt(digits.toString()); + } catch (final NumberFormatException e) { + return 0; + } + } + + private static String getQualifier(final String version) { + final int qualifierIndex = version.indexOf('-'); + return qualifierIndex > 0 ? version.substring(qualifierIndex + 1) : null; + } + + private static int compareVersionPart(final String part1, final String part2) { + final Integer num1 = parseVersionPart(part1); + final Integer num2 = parseVersionPart(part2); + + if (num1 != null && num2 != null) { + return Integer.compare(num1, num2); + } else if (num1 != null) { + return 1; + } else if (num2 != null) { + return -1; + } else { + return part1.compareTo(part2); + } + } + + private static Integer parseVersionPart(final String part) { + try { + return Integer.parseInt(part); + } catch (final NumberFormatException e) { + return null; + } + } + + private static String getBaseVersion(final String version) { + final int qualifierIndex = version.indexOf('-'); + return qualifierIndex > 0 ? version.substring(0, qualifierIndex) : version; + } } diff --git a/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java b/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java new file mode 100644 index 0000000000..5ff0149d5a --- /dev/null +++ b/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java @@ -0,0 +1,368 @@ +/* + * 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.components.connector.util; + +import org.apache.nifi.components.connector.ComponentBundleLookup; +import org.apache.nifi.flow.Bundle; +import org.apache.nifi.flow.ComponentType; +import org.apache.nifi.flow.Position; +import org.apache.nifi.flow.VersionedControllerService; +import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedProcessor; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestVersionedFlowUtils { + + @Nested + class GetLatest { + @Test + void testMinorVersion() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.0"), + new Bundle("group", "artifact", "1.2.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.2.0", latestBundle.getVersion()); + } + + @Test + void testMajorVersion() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.0"), + new Bundle("group", "artifact", "2.0.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2.0.0", latestBundle.getVersion()); + } + + @Test + void testMoreDigitsLater() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0"), + new Bundle("group", "artifact", "1.0.1") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.0.1", latestBundle.getVersion()); + } + + @Test + void testMoreDigitsFirst() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.1"), + new Bundle("group", "artifact", "1.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.0.1", latestBundle.getVersion()); + } + + @Test + void testWithSnapshotAndSameVersion() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.0-SNAPSHOT"), + new Bundle("group", "artifact", "1.0.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.0.0", latestBundle.getVersion()); + } + + @Test + void testWithSnapshotAndDifferentVersion() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.1-SNAPSHOT"), + new Bundle("group", "artifact", "1.0.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.0.1-SNAPSHOT", latestBundle.getVersion()); + } + + @Test + void testEmptyList() { + final List<Bundle> bundles = List.of(); + assertNull(VersionedFlowUtils.getLatest(bundles)); + } + + @Test + void testNonNumericVersionPart() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "4.0.0"), + new Bundle("group", "artifact", "4.0.next") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("4.0.0", latestBundle.getVersion()); + } + + @Test + void testFullyNonNumericVersionVsNumeric() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "1.0.0"), + new Bundle("group", "artifact", "undefined") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("1.0.0", latestBundle.getVersion()); + } + + @Test + void testTwoFullyNonNumericVersions() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "undefined"), + new Bundle("group", "artifact", "unknown") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("unknown", latestBundle.getVersion()); + } + + @Test + void testQualifierOrdering() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "2.0.0-SNAPSHOT"), + new Bundle("group", "artifact", "2.0.0-M1"), + new Bundle("group", "artifact", "2.0.0-M4"), + new Bundle("group", "artifact", "2.0.0-RC1"), + new Bundle("group", "artifact", "2.0.0-RC2"), + new Bundle("group", "artifact", "2.0.0") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2.0.0", latestBundle.getVersion()); + } + + @Test + void testQualifierOrderingWithoutRelease() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "2.0.0-SNAPSHOT"), + new Bundle("group", "artifact", "2.0.0-M1"), + new Bundle("group", "artifact", "2.0.0-M4"), + new Bundle("group", "artifact", "2.0.0-RC1"), + new Bundle("group", "artifact", "2.0.0-RC2") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2.0.0-RC2", latestBundle.getVersion()); + } + + @Test + void testMilestoneVersionsOrdering() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "2.0.0-M1"), + new Bundle("group", "artifact", "2.0.0-M4"), + new Bundle("group", "artifact", "2.0.0-SNAPSHOT") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2.0.0-M4", latestBundle.getVersion()); + } + + @Test + void testCalendarDateFormat() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "2025.12.31"), + new Bundle("group", "artifact", "2026.01.01") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2026.01.01", latestBundle.getVersion()); + } + + @Test + void testCalendarDateFormatWithBuildNumber() { + final List<Bundle> bundles = List.of( + new Bundle("group", "artifact", "2025.12.31.999"), + new Bundle("group", "artifact", "2026.01.01.451") + ); + + final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles); + assertNotNull(latestBundle); + assertEquals("2026.01.01.451", latestBundle.getVersion()); + } + } + + @Nested + class UpdateToLatestBundles { + private static final String PROCESSOR_TYPE = "org.apache.nifi.processors.TestProcessor"; + private static final String SERVICE_TYPE = "org.apache.nifi.services.TestService"; + + @Test + void testLookupReturnsOneOlderBundle() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "1.0.0"))); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("2.0.0", processor.getBundle().getVersion()); + } + + @Test + void testLookupReturnsOneNewerBundle() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "3.0.0"))); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("3.0.0", processor.getBundle().getVersion()); + } + + @Test + void testLookupReturnsMultipleBundlesIncludingSameOlderAndNewer() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of( + new Bundle("group", "artifact", "1.0.0"), + new Bundle("group", "artifact", "2.0.0"), + new Bundle("group", "artifact", "3.0.0"), + new Bundle("group", "artifact", "4.0.0") + )); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("4.0.0", processor.getBundle().getVersion()); + } + + @Test + void testLookupReturnsNoBundles() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of()); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("2.0.0", processor.getBundle().getVersion()); + } + + @Test + void testControllerServiceUpdatedToNewerBundle() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedControllerService service = VersionedFlowUtils.addControllerService(group, SERVICE_TYPE, new Bundle("group", "artifact", "1.5.0"), "Test Service"); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(SERVICE_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "2.5.0"))); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("2.5.0", service.getBundle().getVersion()); + } + + @Test + void testNestedProcessorUpdatedToNewerBundle() { + final VersionedProcessGroup rootGroup = createProcessGroup(); + final VersionedProcessGroup childGroup = createChildProcessGroup(rootGroup, "child-group-id"); + final VersionedProcessor nestedProcessor = VersionedFlowUtils.addProcessor(childGroup, PROCESSOR_TYPE, new Bundle("group", "artifact", "1.0.0"), "Nested Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "5.0.0"))); + + VersionedFlowUtils.updateToLatestBundles(rootGroup, lookup); + + assertEquals("5.0.0", nestedProcessor.getBundle().getVersion()); + } + + @Test + void testLookupReturnsVersionsWithQualifiers() { + final VersionedProcessGroup group = createProcessGroup(); + final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0)); + + final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class); + when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of( + new Bundle("group", "artifact", "2.0.0"), + new Bundle("group", "artifact", "2.0.0-SNAPSHOT"), + new Bundle("group", "artifact", "2.0.0-M1"), + new Bundle("group", "artifact", "2.0.0-RC1") + )); + + VersionedFlowUtils.updateToLatestBundles(group, lookup); + + assertEquals("2.0.0", processor.getBundle().getVersion()); + } + + private VersionedProcessGroup createProcessGroup() { + final VersionedProcessGroup group = new VersionedProcessGroup(); + group.setIdentifier("test-group-id"); + group.setName("Test Process Group"); + group.setProcessors(new HashSet<>()); + group.setProcessGroups(new HashSet<>()); + group.setControllerServices(new HashSet<>()); + group.setConnections(new HashSet<>()); + group.setInputPorts(new HashSet<>()); + group.setOutputPorts(new HashSet<>()); + group.setFunnels(new HashSet<>()); + group.setLabels(new HashSet<>()); + group.setComponentType(ComponentType.PROCESS_GROUP); + return group; + } + + private VersionedProcessGroup createChildProcessGroup(final VersionedProcessGroup parent, final String identifier) { + final VersionedProcessGroup childGroup = new VersionedProcessGroup(); + childGroup.setIdentifier(identifier); + childGroup.setName("Child Process Group"); + childGroup.setGroupIdentifier(parent.getIdentifier()); + childGroup.setProcessors(new HashSet<>()); + childGroup.setProcessGroups(new HashSet<>()); + childGroup.setControllerServices(new HashSet<>()); + childGroup.setConnections(new HashSet<>()); + childGroup.setInputPorts(new HashSet<>()); + childGroup.setOutputPorts(new HashSet<>()); + childGroup.setFunnels(new HashSet<>()); + childGroup.setLabels(new HashSet<>()); + childGroup.setComponentType(ComponentType.PROCESS_GROUP); + parent.getProcessGroups().add(childGroup); + return childGroup; + } + } + +} diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java index 93cb6fd44a..d4af4dcbb2 100644 --- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java +++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java @@ -18,12 +18,13 @@ package org.apache.nifi.mock.connector.server; import org.apache.nifi.asset.AssetManager; -import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder; +import org.apache.nifi.components.connector.ComponentBundleLookup; import org.apache.nifi.components.connector.FlowUpdateException; import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext; +import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder; import org.apache.nifi.components.connector.FrameworkFlowContext; -import org.apache.nifi.components.connector.secrets.SecretsManager; import org.apache.nifi.components.connector.components.FlowContext; +import org.apache.nifi.components.connector.secrets.SecretsManager; import org.apache.nifi.flow.VersionedExternalFlow; import org.apache.nifi.flow.VersionedProcessGroup; import org.apache.nifi.flow.VersionedProcessor; @@ -36,6 +37,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni private final ComponentLog componentLog; private final SecretsManager secretsManager; private final AssetManager assetManager; + private final ComponentBundleLookup componentBundleLookup; private final MockExtensionMapper mockExtensionMapper; @@ -46,6 +48,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni this.secretsManager = builder.secretsManager; this.assetManager = builder.assetManager; this.mockExtensionMapper = builder.mockExtensionMapper; + this.componentBundleLookup = builder.componentBundleLookup; } @@ -64,6 +67,11 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni return componentLog; } + @Override + public ComponentBundleLookup getComponentBundleLookup() { + return componentBundleLookup; + } + @Override public SecretsManager getSecretsManager() { return secretsManager; @@ -118,6 +126,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni private ComponentLog componentLog; private SecretsManager secretsManager; private AssetManager assetManager; + private ComponentBundleLookup componentBundleLookup; public Builder(final MockExtensionMapper mockExtensionMapper) { this.mockExtensionMapper = mockExtensionMapper; @@ -147,6 +156,12 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni return this; } + @Override + public Builder componentBundleLookup(final ComponentBundleLookup bundleLookup) { + this.componentBundleLookup = bundleLookup; + return this; + } + @Override public Builder assetManager(final AssetManager assetManager) { this.assetManager = assetManager; diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java index 030e333a84..9ef92941a8 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java @@ -33,5 +33,7 @@ public interface FrameworkConnectorInitializationContextBuilder { FrameworkConnectorInitializationContextBuilder secretsManager(SecretsManager secretsManager); + FrameworkConnectorInitializationContextBuilder componentBundleLookup(ComponentBundleLookup bundleLookup); + FrameworkConnectorInitializationContext build(); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java new file mode 100644 index 0000000000..84d664d80b --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java @@ -0,0 +1,45 @@ +/* + * 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.components.connector; + +import org.apache.nifi.bundle.BundleCoordinate; +import org.apache.nifi.flow.Bundle; +import org.apache.nifi.nar.ExtensionManager; + +import java.util.List; + +public class StandardComponentBundleLookup implements ComponentBundleLookup { + private final ExtensionManager extensionManager; + + public StandardComponentBundleLookup(final ExtensionManager extensionManager) { + this.extensionManager = extensionManager; + } + + @Override + public List<Bundle> getAvailableBundles(final String componentType) { + final List<org.apache.nifi.bundle.Bundle> bundles = extensionManager.getBundles(componentType); + return bundles.stream() + .map(this::convertBundle) + .toList(); + } + + private Bundle convertBundle(final org.apache.nifi.bundle.Bundle bundle) { + final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate(); + return new Bundle(coordinate.getGroup(), coordinate.getId(), coordinate.getVersion()); + } +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java index c31cad237f..ab2dcbf399 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java @@ -30,6 +30,7 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto private final ComponentLog componentLog; private final SecretsManager secretsManager; private final AssetManager assetManager; + private final ComponentBundleLookup componentBundleLookup; private StandardConnectorInitializationContext(final Builder builder) { @@ -38,9 +39,9 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto this.componentLog = builder.componentLog; this.secretsManager = builder.secretsManager; this.assetManager = builder.assetManager; + this.componentBundleLookup = builder.componentBundleLookup; } - @Override public String getIdentifier() { return identifier; @@ -56,6 +57,11 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto return componentLog; } + @Override + public ComponentBundleLookup getComponentBundleLookup() { + return componentBundleLookup; + } + @Override public SecretsManager getSecretsManager() { return secretsManager; @@ -90,6 +96,7 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto private ComponentLog componentLog; private SecretsManager secretsManager; private AssetManager assetManager; + private ComponentBundleLookup componentBundleLookup; @Override public Builder identifier(final String identifier) { @@ -121,6 +128,12 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto return this; } + @Override + public Builder componentBundleLookup(final ComponentBundleLookup bundleLookup) { + this.componentBundleLookup = bundleLookup; + return this; + } + @Override public StandardConnectorInitializationContext build() { return new StandardConnectorInitializationContext(this); 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 6d985e297d..64f06ded7c 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 @@ -28,6 +28,7 @@ import org.apache.nifi.bundle.Bundle; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.connector.ComponentBundleLookup; import org.apache.nifi.components.connector.ConfigurationStep; import org.apache.nifi.components.connector.Connector; import org.apache.nifi.components.connector.ConnectorDetails; @@ -147,6 +148,7 @@ public class ExtensionBuilder { private ConnectorStateTransition connectorStateTransition; private FrameworkConnectorInitializationContextBuilder connectorInitializationContextBuilder; private ConnectorValidationTrigger connectorValidationTrigger; + private ComponentBundleLookup componentBundleLookup; public ExtensionBuilder type(final String type) { this.type = type; @@ -281,6 +283,12 @@ public class ExtensionBuilder { return this; } + public ExtensionBuilder componentBundleLookup(final ComponentBundleLookup componentBundleLookup) { + this.componentBundleLookup = componentBundleLookup; + return this; + } + + public ProcessorNode buildProcessor() { requireNonNull(identifier, "Processor ID"); requireNonNull(type, "Processor Type"); @@ -574,7 +582,7 @@ public class ExtensionBuilder { connectorNode.initializeConnector(initContext); return connectorNode; - } + } private void initializeDefaultValues(final Connector connector, final FrameworkFlowContext flowContext) { try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(), identifier)) { @@ -605,6 +613,7 @@ public class ExtensionBuilder { .componentLog(componentLog) .secretsManager(flowController.getConnectorRepository().getSecretsManager()) .assetManager(flowController.getConnectorAssetManager()) + .componentBundleLookup(componentBundleLookup) .build(); } 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 d3874eb5cb..3826c9a371 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 @@ -27,12 +27,14 @@ import org.apache.nifi.bundle.Bundle; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.connector.ComponentBundleLookup; import org.apache.nifi.components.connector.Connector; import org.apache.nifi.components.connector.ConnectorNode; import org.apache.nifi.components.connector.ConnectorRepository; import org.apache.nifi.components.connector.ConnectorStateTransition; import org.apache.nifi.components.connector.FlowContextFactory; import org.apache.nifi.components.connector.ProcessGroupFactory; +import org.apache.nifi.components.connector.StandardComponentBundleLookup; import org.apache.nifi.components.connector.StandardConnectorConfigurationContext; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.ConnectableType; @@ -763,6 +765,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, id); final FlowContextFactory flowContextFactory = new FlowControllerFlowContextFactory(flowController, managedRootGroup, activeConfigurationContext, processGroupFactory); + final ComponentBundleLookup componentBundleLookup = new StandardComponentBundleLookup(extensionManager); final ConnectorNode connectorNode = new ExtensionBuilder() .identifier(id) @@ -776,6 +779,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana .connectorStateTransition(stateTransition) .connectorInitializationContextBuilder(flowController.getConnectorRepository().createInitializationContextBuilder()) .connectorValidationTrigger(flowController.getConnectorValidationTrigger()) + .componentBundleLookup(componentBundleLookup) .buildConnector(firstTimeAdded); // Establish the Connector as the parent authorizable of the managed root group diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java index db43577038..d8763e0587 100644 --- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java +++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java @@ -100,9 +100,6 @@ public class CalculateConnector extends AbstractConnector { private Calculation calculation; private int result; - public CalculatedResult() { - } - public Calculation getCalculation() { return calculation; }
