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 9c9e369676 NIFI-15451: Added ability for Connectors to retrieve
bundles availabl… (#10756)
9c9e369676 is described below
commit 9c9e3696766fa15d4f2739871fc3efbed32236c2
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 | 9 +
.../nifi/controller/flow/StandardFlowManager.java | 4 +
.../tests/system/CalculateConnector.java | 3 -
9 files changed, 671 insertions(+), 6 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 989956fd0d..2bbf80a1ac 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");
@@ -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;
}