From 9e34cc99c1178764772073b7f21dd499e46a01b5 Mon Sep 17 00:00:00 2001 From: Venkateshwaran Shanmugham Date: Thu, 28 May 2026 21:15:50 +0530 Subject: [PATCH] NIFI-15889 Cache Parameter Context name mapping to fix O(N_PG x N_PC^2) startup synchronization --- ...tandardVersionedComponentSynchronizer.java | 2 +- .../StandardParameterContextManager.java | 61 ++++++++--- .../TestStandardParameterContextManager.java | 102 ++++++++++++++++++ .../parameter/ParameterContextManager.java | 9 ++ .../dao/impl/StandardParameterContextDAO.java | 2 +- 5 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContextManager.java diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java index d5f1db9d7bee..5d8ee1a3ed86 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java @@ -1854,7 +1854,7 @@ public void synchronize(final ParameterContext parameterContext, final Versioned } parameterContext.setParameters(updatedParameters); - parameterContext.setName(proposed.getName()); + contextManager.setParameterContextName(parameterContext.getIdentifier(), proposed.getName()); parameterContext.setDescription(proposed.getDescription()); parameterContext.setInheritedParameterContexts(inheritedContexts); LOG.info("Successfully synchronized {} by updating it to match the proposed version", parameterContext); diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java index 8e3983bec427..9fae748da967 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java @@ -16,19 +16,20 @@ */ package org.apache.nifi.parameter; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; public class StandardParameterContextManager implements ParameterContextManager { private final Map parameterContexts = new HashMap<>(); + private final Map nameIndex = new HashMap<>(); + private volatile Map cachedNameMapping = Collections.emptyMap(); @Override - public boolean hasParameterContext(final String id) { + public synchronized boolean hasParameterContext(final String id) { return parameterContexts.get(id) != null; } @@ -41,25 +42,55 @@ public synchronized ParameterContext getParameterContext(final String id) { public synchronized void addParameterContext(final ParameterContext parameterContext) { Objects.requireNonNull(parameterContext); - if (parameterContexts.containsKey(parameterContext.getIdentifier())) { - if (!(parameterContexts.get(parameterContext.getIdentifier()) instanceof ReferenceOnlyParameterContext)) { - throw new IllegalStateException("Cannot add Parameter Context because another Parameter Context already exists with the same ID"); - } + final ParameterContext existingById = parameterContexts.get(parameterContext.getIdentifier()); + if (existingById != null && !(existingById instanceof ReferenceOnlyParameterContext)) { + throw new IllegalStateException("Cannot add Parameter Context because another Parameter Context already exists with the same ID"); } - for (final ParameterContext context : parameterContexts.values()) { - if (context.getName().equals(parameterContext.getName())) { - throw new IllegalStateException("Cannot add Parameter Context because another Parameter Context already exists with the name '" + parameterContext + "'"); - } + final ParameterContext existingByName = nameIndex.get(parameterContext.getName()); + if (existingByName != null && existingByName != existingById) { + throw new IllegalStateException("Cannot add Parameter Context because another Parameter Context already exists with the name '" + parameterContext + "'"); + } + + if (existingById instanceof ReferenceOnlyParameterContext) { + nameIndex.remove(existingById.getName(), existingById); } parameterContexts.put(parameterContext.getIdentifier(), parameterContext); + nameIndex.put(parameterContext.getName(), parameterContext); + updateCachedNameMapping(); } @Override public synchronized ParameterContext removeParameterContext(final String parameterContextId) { Objects.requireNonNull(parameterContextId); - return parameterContexts.remove(parameterContextId); + final ParameterContext removed = parameterContexts.remove(parameterContextId); + if (removed != null) { + nameIndex.remove(removed.getName(), removed); + updateCachedNameMapping(); + } + return removed; + } + + @Override + public synchronized void setParameterContextName(final String parameterContextId, final String name) { + Objects.requireNonNull(parameterContextId); + Objects.requireNonNull(name); + + final ParameterContext parameterContext = parameterContexts.get(parameterContextId); + if (parameterContext == null) { + throw new IllegalStateException("Cannot rename Parameter Context because no Parameter Context exists with the specified ID"); + } + + final ParameterContext existingByName = nameIndex.get(name); + if (existingByName != null && existingByName != parameterContext) { + throw new IllegalStateException("Cannot rename Parameter Context because another Parameter Context already exists with the name '" + name + "'"); + } + + nameIndex.remove(parameterContext.getName(), parameterContext); + parameterContext.setName(name); + nameIndex.put(parameterContext.getName(), parameterContext); + updateCachedNameMapping(); } @Override @@ -69,6 +100,10 @@ public synchronized Set getParameterContexts() { @Override public Map getParameterContextNameMapping() { - return parameterContexts.values().stream().collect(Collectors.toMap(ParameterContext::getName, Function.identity())); + return cachedNameMapping; + } + + private void updateCachedNameMapping() { + cachedNameMapping = Collections.unmodifiableMap(new HashMap<>(nameIndex)); } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContextManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContextManager.java new file mode 100644 index 000000000000..88367f257485 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContextManager.java @@ -0,0 +1,102 @@ +/* + * 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.parameter; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestStandardParameterContextManager { + + @Test + public void testNameMappingUpdatedOnAddRemoveAndRename() { + final StandardParameterContextManager manager = new StandardParameterContextManager(); + final ParameterContext firstContext = createParameterContext("id-1", "Context 1"); + + manager.addParameterContext(firstContext); + + final Map initialNameMapping = manager.getParameterContextNameMapping(); + assertSame(firstContext, initialNameMapping.get("Context 1")); + assertThrows(UnsupportedOperationException.class, () -> initialNameMapping.put("Other", firstContext)); + assertSame(initialNameMapping, manager.getParameterContextNameMapping()); + + manager.setParameterContextName("id-1", "Renamed"); + + final Map renamedMapping = manager.getParameterContextNameMapping(); + assertFalse(renamedMapping.containsKey("Context 1")); + assertSame(firstContext, renamedMapping.get("Renamed")); + + final ParameterContext secondContext = createParameterContext("id-2", "Context 2"); + manager.addParameterContext(secondContext); + assertThrows(IllegalStateException.class, () -> manager.setParameterContextName("id-2", "Renamed")); + + assertSame(firstContext, manager.removeParameterContext("id-1")); + final Map removedMapping = manager.getParameterContextNameMapping(); + assertFalse(removedMapping.containsKey("Renamed")); + assertSame(secondContext, removedMapping.get("Context 2")); + } + + @Test + public void testReferenceOnlyParameterContextReplacementRemovesPlaceholderName() { + final StandardParameterContextManager manager = new StandardParameterContextManager(); + final ReferenceOnlyParameterContext referenceOnlyContext = new ReferenceOnlyParameterContext("id-1"); + + manager.addParameterContext(referenceOnlyContext); + final String placeholderName = referenceOnlyContext.getName(); + assertSame(referenceOnlyContext, manager.getParameterContextNameMapping().get(placeholderName)); + + final ParameterContext realContext = createParameterContext("id-1", "Real Context"); + manager.addParameterContext(realContext); + + final Map nameMapping = manager.getParameterContextNameMapping(); + assertFalse(nameMapping.containsKey(placeholderName)); + assertSame(realContext, nameMapping.get("Real Context")); + assertSame(realContext, manager.getParameterContext("id-1")); + + assertSame(realContext, manager.removeParameterContext("id-1")); + assertTrue(manager.getParameterContextNameMapping().isEmpty()); + } + + @Test + public void testDuplicateNameRejected() { + final StandardParameterContextManager manager = new StandardParameterContextManager(); + manager.addParameterContext(createParameterContext("id-1", "Context")); + + assertThrows(IllegalStateException.class, () -> manager.addParameterContext(createParameterContext("id-2", "Context"))); + } + + @Test + public void testDuplicateIdentifierRejected() { + final StandardParameterContextManager manager = new StandardParameterContextManager(); + manager.addParameterContext(createParameterContext("id-1", "Context 1")); + + assertThrows(IllegalStateException.class, () -> manager.addParameterContext(createParameterContext("id-1", "Context 2"))); + } + + private static ParameterContext createParameterContext(final String id, final String name) { + return new StandardParameterContext.Builder() + .id(id) + .name(name) + .parameterReferenceManager(ParameterReferenceManager.EMPTY) + .build(); + } +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContextManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContextManager.java index 5158f3bc79a0..df0504f1a359 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContextManager.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContextManager.java @@ -25,6 +25,15 @@ public interface ParameterContextManager extends ParameterContextLookup { ParameterContext removeParameterContext(String parameterContextId); + default void setParameterContextName(final String parameterContextId, final String name) { + final ParameterContext parameterContext = getParameterContext(parameterContextId); + if (parameterContext == null) { + throw new IllegalStateException("Cannot rename Parameter Context because no Parameter Context exists with the specified ID"); + } + + parameterContext.setName(name); + } + Set getParameterContexts(); Map getParameterContextNameMapping(); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java index 090368245eb4..294341fb17b2 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java @@ -287,7 +287,7 @@ public ParameterContext updateParameterContext(final ParameterContextDTO paramet if (parameterContextDto.getName() != null) { verifyNoNamingConflict(parameterContextDto.getName(), parameterContextDto.getId()); - context.setName(parameterContextDto.getName()); + flowManager.getParameterContextManager().setParameterContextName(parameterContextDto.getId(), parameterContextDto.getName()); } if (parameterContextDto.getDescription() != null) {