This is an automated email from the ASF dual-hosted git repository.
exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 14fa8a2fb03 NIFI-16286 Prevent stale Parameter Context provenance from
breaking Context retrieval (#11618)
14fa8a2fb03 is described below
commit 14fa8a2fb03e7a11320119054bb8cc05a8202b8f
Author: Pierre Villard <[email protected]>
AuthorDate: Mon Sep 7 16:14:57 2026 +0200
NIFI-16286 Prevent stale Parameter Context provenance from breaking Context
retrieval (#11618)
Signed-off-by: David Handermann <[email protected]>
---
.../apache/nifi/web/StandardNiFiServiceFacade.java | 11 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 16 +-
.../web/dao/impl/StandardParameterContextDAO.java | 4 +-
.../nifi/web/StandardNiFiServiceFacadeTest.java | 130 ++++++++++++++++
.../apache/nifi/web/api/dto/DtoFactoryTest.java | 60 ++++++++
.../dao/impl/TestStandardParameterContextDAO.java | 167 +++++++++++++++++++--
.../system/parameters/ParameterContextIT.java | 33 ++++
7 files changed, 402 insertions(+), 19 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 786b008514a..90db89eea4d 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -1833,7 +1833,16 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
return parameterContext;
}
- return parameterContextDAO.getParameterContext(sourceContextId);
+ if (parameterContextDAO.hasParameterContext(sourceContextId)) {
+ try {
+ return
parameterContextDAO.getParameterContext(sourceContextId);
+ } catch (final ResourceNotFoundException ignored) {
+ }
+ }
+
+ logger.warn("Parameter [{}] in Parameter Context [{}] references
missing source Parameter Context [{}] and is not locally owned; reporting as
locally defined",
+ parameter.getDescriptor().getName(),
parameterContext.getIdentifier(), sourceContextId);
+ return parameterContext;
}
private void addReferencingComponents(final ControllerServiceNode service,
final Set<ComponentNode> affectedComponents, final List<ParameterDTO>
affectedParameterDtos,
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 9fe3af8f41e..46e8ab35d7a 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
@@ -198,6 +198,7 @@ import org.apache.nifi.util.FlowDifferenceFilters;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.security.MessageDigestUtils;
import org.apache.nifi.web.FlowModification;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import
org.apache.nifi.web.api.dto.SystemDiagnosticsSnapshotDTO.ResourceClaimDetailsDTO;
import org.apache.nifi.web.api.dto.action.ActionDTO;
@@ -1654,8 +1655,19 @@ public final class DtoFactory {
return fromGraph;
}
- final ParameterContext fromLookup =
parameterContextLookup.getParameterContext(sourceId);
- return fromLookup != null ? fromLookup : parameterContext;
+ if (parameterContextLookup.hasParameterContext(sourceId)) {
+ try {
+ final ParameterContext fromLookup =
parameterContextLookup.getParameterContext(sourceId);
+ if (fromLookup != null) {
+ return fromLookup;
+ }
+ } catch (final ResourceNotFoundException ignored) {
+ }
+ }
+
+ logger.warn("Parameter [{}] in Parameter Context [{}] references
missing source Parameter Context [{}]; reporting as locally defined",
+ parameter.getDescriptor().getName(),
parameterContext.getIdentifier(), sourceId);
+ return parameterContext;
}
private ParameterContext findInheritedParameterContext(final
ParameterContext parameterContext, final String sourceId, final Set<String>
visited) {
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 090368245eb..71acf72efab 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
@@ -222,8 +222,6 @@ public class StandardParameterContextDAO implements
ParameterContextDAO {
final String dtoValue = dto.getValue();
final List<AssetReferenceDTO> referencedAssets =
dto.getReferencedAssets();
final boolean referencesAsset = referencedAssets != null &&
!referencedAssets.isEmpty();
- final String parameterContextId = dto.getParameterContext() == null ?
null : dto.getParameterContext().getId();
-
final String value;
List<Asset> assets = null;
if (dtoValue == null && !referencesAsset &&
Boolean.TRUE.equals(dto.getValueRemoved())) {
@@ -245,7 +243,7 @@ public class StandardParameterContextDAO implements
ParameterContextDAO {
.name(dto.getName())
.description(dto.getDescription())
.sensitive(Boolean.TRUE.equals(dto.getSensitive()))
- .parameterContextId(parameterContextId)
+ .parameterContextId(null)
.value(value)
.referencedAssets(assets)
.provided(dto.getProvided())
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
index 97f7d20ca0b..079f8cf8629 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
@@ -2531,6 +2531,7 @@ public class StandardNiFiServiceFacadeTest {
final ParameterContextDAO parameterContextDAO =
mock(ParameterContextDAO.class);
when(parameterContextDAO.getParameterContext(targetContextId)).thenReturn(targetContext);
when(parameterContextDAO.getParameterContext(inheritedContextId)).thenReturn(inheritedContext);
+
when(parameterContextDAO.hasParameterContext(inheritedContextId)).thenReturn(true);
when(parameterContextDAO.getParameters(any(ParameterContextDTO.class),
same(targetContext))).thenReturn(Map.of());
when(parameterContextDAO.getInheritedParameterContexts(any(ParameterContextDTO.class))).thenReturn(List.of(inheritedContext));
when(targetContext.getEffectiveParameterUpdates(anyMap(),
eq(List.of(inheritedContext))))
@@ -2623,6 +2624,134 @@ public class StandardNiFiServiceFacadeTest {
assertFalse(secondPassParameters.containsKey(aliasParameterName));
}
+ @Test
+ public void
testGetComponentsAffectedByParameterContextUpdateTwiceFallsBackWhenSourceContextDisappears()
{
+ final String targetContextId = "target-context";
+ final String inheritedContextId = "inherited-context";
+ final String inheritedParameterName = "inherited-provider-param";
+ final String inheritedParameterValue = "provider-secret-value";
+ final String processorId = "processor-id";
+
+ final ParameterDescriptor inheritedDescriptor = new
ParameterDescriptor.Builder().name(inheritedParameterName).build();
+ final Parameter inheritedParameter = new Parameter.Builder()
+ .descriptor(inheritedDescriptor)
+ .value(inheritedParameterValue)
+ .provided(true)
+ .parameterContextId(inheritedContextId)
+ .build();
+ final Parameter maskedInheritedParameter = new Parameter.Builder()
+ .descriptor(new
ParameterDescriptor.Builder().name(inheritedParameterName).sensitive(true).build())
+ .value(inheritedParameterValue)
+ .provided(true)
+ .parameterContextId(inheritedContextId)
+ .build();
+
+ final ParameterContext inheritedContext = mock(ParameterContext.class);
+ when(inheritedContext.getIdentifier()).thenReturn(inheritedContextId);
+ when(inheritedContext.getName()).thenReturn("Inherited Context");
+
when(inheritedContext.getInheritedParameterContexts()).thenReturn(List.of());
+
+ final ParameterContext targetContext = mock(ParameterContext.class);
+ when(targetContext.getIdentifier()).thenReturn(targetContextId);
+ when(targetContext.getName()).thenReturn("Target Context");
+ when(targetContext.getParameters()).thenReturn(Map.of());
+
when(targetContext.getParameterReferenceManager()).thenReturn(ParameterReferenceManager.EMPTY);
+
when(targetContext.getInheritedParameterContexts()).thenReturn(List.of(inheritedContext));
+
+ final ParameterContextDAO parameterContextDAO =
mock(ParameterContextDAO.class);
+
when(parameterContextDAO.getParameterContext(targetContextId)).thenReturn(targetContext);
+ when(parameterContextDAO.getParameters(any(ParameterContextDTO.class),
same(targetContext))).thenReturn(Map.of());
+
when(parameterContextDAO.getInheritedParameterContexts(any(ParameterContextDTO.class))).thenReturn(List.of(inheritedContext));
+ when(targetContext.getEffectiveParameterUpdates(anyMap(),
eq(List.of(inheritedContext))))
+ .thenReturn(Map.of(inheritedParameterName, inheritedParameter))
+ .thenReturn(Map.of(inheritedParameterName,
maskedInheritedParameter));
+
when(parameterContextDAO.hasParameterContext(inheritedContextId)).thenReturn(true,
true);
+ when(parameterContextDAO.getParameterContext(inheritedContextId))
+ .thenReturn(inheritedContext)
+ .thenThrow(new ResourceNotFoundException("Source context was
removed"));
+
+ final ProcessorNode processorNode = mock(ProcessorNode.class);
+ when(processorNode.isRunning()).thenReturn(true);
+
when(processorNode.getReferencedParameterNames()).thenReturn(Set.of(inheritedParameterName));
+ when(processorNode.getIdentifier()).thenReturn(processorId);
+ when(processorNode.getName()).thenReturn("Processor");
+ when(processorNode.getProcessGroupIdentifier()).thenReturn("group-id");
+
when(processorNode.getDesiredState()).thenReturn(ScheduledState.STOPPED);
+ when(processorNode.getActiveThreadCount()).thenReturn(0);
+ when(processorNode.getValidationErrors()).thenReturn(List.of());
+
+ final ProcessGroup referencingGroup = mock(ProcessGroup.class);
+ when(referencingGroup.getParameterContext()).thenReturn(targetContext);
+
when(referencingGroup.getProcessors()).thenReturn(List.of(processorNode));
+
when(referencingGroup.getControllerServices(false)).thenReturn(Set.of());
+ when(referencingGroup.getExecutionEngine()).thenReturn(null);
+ when(referencingGroup.getParent()).thenReturn(null);
+ when(referencingGroup.getIdentifier()).thenReturn("group-id");
+ when(referencingGroup.getName()).thenReturn("Group");
+ when(referencingGroup.isAuthorized(any(), any(),
any())).thenReturn(false);
+ when(processorNode.getProcessGroup()).thenReturn(referencingGroup);
+
+ final ProcessGroup rootGroup = mock(ProcessGroup.class);
+ when(processGroupDAO.getProcessGroup("root")).thenReturn(rootGroup);
+ when(rootGroup.findAllProcessGroups(any())).thenAnswer(invocation -> {
+ final java.util.function.Predicate<ProcessGroup> predicate =
invocation.getArgument(0);
+ return predicate.test(referencingGroup) ?
List.of(referencingGroup) : List.of();
+ });
+
+ final ParameterContextReferenceDTO inheritedReference = new
ParameterContextReferenceDTO();
+ inheritedReference.setId(inheritedContextId);
+ inheritedReference.setName("Inherited Context");
+ final ParameterContextReferenceEntity inheritedReferenceEntity = new
ParameterContextReferenceEntity();
+ inheritedReferenceEntity.setId(inheritedContextId);
+ inheritedReferenceEntity.setComponent(inheritedReference);
+
+ final ParameterContextDTO parameterContextDto = new
ParameterContextDTO();
+ parameterContextDto.setId(targetContextId);
+ parameterContextDto.setName("Target Context");
+ parameterContextDto.setParameters(new HashSet<>());
+
parameterContextDto.setInheritedParameterContexts(List.of(inheritedReferenceEntity));
+
+ serviceFacade.setParameterContextDAO(parameterContextDAO);
+ serviceFacade.setRevisionManager(new NaiveRevisionManager());
+ final DtoFactory dtoFactory = new DtoFactory();
+ dtoFactory.setEntityFactory(new EntityFactory());
+ final BulletinRepository dtoBulletinRepository =
mock(BulletinRepository.class);
+ when(dtoBulletinRepository.findBulletinsForSource(anyString(),
anyString())).thenReturn(List.of());
+ dtoFactory.setBulletinRepository(dtoBulletinRepository);
+ serviceFacade.setDtoFactory(dtoFactory);
+
+ final Set<AffectedComponentEntity> firstAffected =
serviceFacade.getComponentsAffectedByParameterContextUpdate(List.of(parameterContextDto));
+ assertEquals(1, firstAffected.size());
+ assertEquals(processorId, firstAffected.iterator().next().getId());
+
+ final Map<String, ParameterDTO> firstPassParameters =
parameterContextDto.getParameters().stream()
+ .map(ParameterEntity::getParameter)
+ .collect(Collectors.toMap(ParameterDTO::getName,
Function.identity()));
+ final ParameterDTO firstPassParameter =
firstPassParameters.get(inheritedParameterName);
+ assertTrue(firstPassParameter.getInherited());
+ assertTrue(firstPassParameter.getProvided());
+ assertEquals(inheritedContextId,
firstPassParameter.getParameterContext().getId());
+ assertEquals(inheritedParameterValue, firstPassParameter.getValue());
+
+ final Set<AffectedComponentEntity> secondAffected =
serviceFacade.getComponentsAffectedByParameterContextUpdate(List.of(parameterContextDto));
+ assertEquals(1, secondAffected.size());
+ assertEquals(processorId, secondAffected.iterator().next().getId());
+
+ final Map<String, ParameterDTO> secondPassParameters =
parameterContextDto.getParameters().stream()
+ .map(ParameterEntity::getParameter)
+ .collect(Collectors.toMap(ParameterDTO::getName,
Function.identity()));
+ final ParameterDTO secondPassParameter =
secondPassParameters.get(inheritedParameterName);
+ assertFalse(secondPassParameter.getInherited());
+ assertTrue(secondPassParameter.getProvided());
+ assertEquals(targetContextId,
secondPassParameter.getParameterContext().getId());
+ assertEquals(inheritedParameterValue, secondPassParameter.getValue());
+ assertEquals(1, secondPassParameter.getReferencingComponents().size());
+ assertEquals(processorId,
secondPassParameter.getReferencingComponents().iterator().next().getId());
+
+ verify(parameterContextDAO,
times(2)).hasParameterContext(inheritedContextId);
+ verify(parameterContextDAO,
times(2)).getParameterContext(inheritedContextId);
+ }
+
@Test
public void
testGetComponentsAffectedByParameterContextUpdateDoesNotAddSensitiveLocalAliasToUpdate()
{
final String targetContextId = "target-context";
@@ -3154,4 +3283,5 @@ public class StandardNiFiServiceFacadeTest {
serviceFacade.setParameterContextDAO(parameterContextDAO);
return assetManager;
}
+
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
index b8d84fb4d6b..a6f4fb1cd81 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
@@ -50,6 +50,7 @@ import org.apache.nifi.processor.Relationship;
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
import org.apache.nifi.registry.flow.diff.DifferenceType;
import org.apache.nifi.registry.flow.diff.FlowDifference;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.revision.RevisionManager;
@@ -878,6 +879,7 @@ public class DtoFactoryTest {
.build();
final ParameterContextLookup lookup =
mock(ParameterContextLookup.class);
+ when(lookup.hasParameterContext(externalId)).thenReturn(true);
when(lookup.getParameterContext(externalId)).thenReturn(externalContext);
final DtoFactory dtoFactory = newDtoFactoryForParameters();
@@ -886,6 +888,7 @@ public class DtoFactoryTest {
assertTrue(dto.getInherited());
assertEquals(externalId, dto.getParameterContext().getId());
+ verify(lookup).hasParameterContext(externalId);
verify(lookup).getParameterContext(externalId);
}
@@ -909,6 +912,60 @@ public class DtoFactoryTest {
assertEquals(contextId, dto.getParameterContext().getId());
}
+ @Test
+ void
testCreateParameterDtoFallsBackToCurrentContextWhenLookupReportsMissingSourceWithoutCallingGetter()
{
+ final String contextId = "context-1";
+ final String missingSourceId = "context-missing";
+ final String parameterName = "param-name";
+
+ final ParameterContext parameterContext =
createMockParameterContext(contextId, "context-1-name",
Collections.emptyList());
+ final Parameter parameter = new Parameter.Builder()
+ .name(parameterName)
+ .value("param-value")
+ .parameterContextId(missingSourceId)
+ .build();
+
+ final ParameterContextLookup lookup =
mock(ParameterContextLookup.class);
+ when(lookup.hasParameterContext(missingSourceId)).thenReturn(false);
+ when(lookup.getParameterContext(missingSourceId)).thenThrow(new
AssertionError("Lookup getter should not be called for a missing source
context"));
+
+ final DtoFactory dtoFactory = newDtoFactoryForParameters();
+ final ParameterDTO dto =
dtoFactory.createParameterDto(parameterContext, parameter,
mock(RevisionManager.class), lookup);
+
+ assertFalse(dto.getInherited());
+ assertEquals(contextId, dto.getParameterContext().getId());
+
+ verify(lookup).hasParameterContext(missingSourceId);
+ verify(lookup, never()).getParameterContext(missingSourceId);
+ }
+
+ @Test
+ void
testCreateParameterDtoFallsBackToCurrentContextWhenSourceDisappearsDuringLookup()
{
+ final String contextId = "context-1";
+ final String missingSourceId = "context-missing";
+ final String parameterName = "param-name";
+
+ final ParameterContext parameterContext =
createMockParameterContext(contextId, "context-1-name",
Collections.emptyList());
+ final Parameter parameter = new Parameter.Builder()
+ .name(parameterName)
+ .value("param-value")
+ .parameterContextId(missingSourceId)
+ .build();
+
+ final ParameterContextLookup lookup =
mock(ParameterContextLookup.class);
+ when(lookup.hasParameterContext(missingSourceId)).thenReturn(true);
+ when(lookup.getParameterContext(missingSourceId)).thenThrow(new
ResourceNotFoundException("Source context was removed"));
+
+ final DtoFactory dtoFactory = newDtoFactoryForParameters();
+ final ParameterDTO dto =
dtoFactory.createParameterDto(parameterContext, parameter,
mock(RevisionManager.class), lookup);
+
+ assertFalse(dto.getInherited());
+ assertEquals(contextId, dto.getParameterContext().getId());
+
+ verify(lookup).hasParameterContext(missingSourceId);
+ verify(lookup).getParameterContext(missingSourceId);
+ }
+
@Test
void
testCreateParameterDtoResolvesSourceContextFromDiamondInheritanceGraph() {
final String contextAId = "context-a";
@@ -955,6 +1012,7 @@ public class DtoFactoryTest {
final ParameterContext fallbackContext =
createMockParameterContext(missingId, "missing", Collections.emptyList());
final ParameterContextLookup lookup =
mock(ParameterContextLookup.class);
+ when(lookup.hasParameterContext(missingId)).thenReturn(true);
when(lookup.getParameterContext(missingId)).thenReturn(fallbackContext);
final DtoFactory dtoFactory = newDtoFactoryForParameters();
@@ -963,6 +1021,7 @@ public class DtoFactoryTest {
assertTrue(dto.getInherited());
assertEquals(missingId, dto.getParameterContext().getId());
+ verify(lookup).hasParameterContext(missingId);
verify(lookup).getParameterContext(missingId);
}
@@ -1002,4 +1061,5 @@ public class DtoFactoryTest {
when(context.getName()).thenReturn(name);
when(context.getParameterReferenceManager()).thenReturn(ParameterReferenceManager.EMPTY);
}
+
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java
index 60531f909d7..e05019cca0d 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java
@@ -17,6 +17,8 @@
package org.apache.nifi.web.dao.impl;
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.Authorizer;
@@ -31,6 +33,7 @@ import org.apache.nifi.parameter.ParameterReferenceManager;
import org.apache.nifi.parameter.StandardParameterContext;
import org.apache.nifi.parameter.StandardParameterContextManager;
import org.apache.nifi.parameter.StandardParameterReferenceManager;
+import org.apache.nifi.web.api.dto.AssetReferenceDTO;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO;
import org.apache.nifi.web.api.dto.ParameterDTO;
@@ -46,25 +49,43 @@ import
org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TestStandardParameterContextDAO {
+ private static final String CONTEXT_ID = "id";
+ private static final String CONTEXT_NAME = "Context";
+ private static final String INHERITED_CONTEXT_ID = "inherited-id";
+
private StandardParameterContextDAO dao;
@Mock(answer = RETURNS_DEEP_STUBS)
private FlowController flowController;
+ @Mock
+ private AssetManager assetManager;
+
@Mock
private Authentication authentication;
@@ -74,26 +95,27 @@ public class TestStandardParameterContextDAO {
@BeforeEach
void setUp() {
dao = new StandardParameterContextDAO();
+ when(flowController.getAssetManager()).thenReturn(assetManager);
dao.setFlowController(flowController);
dao.setAuthorizer(authorizer);
-
when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
+
lenient().when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
final SecurityContext securityContext =
SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
final NiFiUser user = new
StandardNiFiUser.Builder().identity("user").build();
final NiFiUserDetails userDetail = new NiFiUserDetails(user);
- when(authentication.getPrincipal()).thenReturn(userDetail);
+ lenient().when(authentication.getPrincipal()).thenReturn(userDetail);
final ParameterReferenceManager parameterReferenceManager = new
StandardParameterReferenceManager(() ->
flowController.getFlowManager().getRootGroup());
final FlowManager flowManager = flowController.getFlowManager();
final StandardParameterContextManager parameterContextLookup = new
StandardParameterContextManager();
when(flowManager.getParameterContextManager()).thenReturn(parameterContextLookup);
- parameterContextLookup.addParameterContext(new
StandardParameterContext.Builder().id("id")
- .name("Context")
+ parameterContextLookup.addParameterContext(new
StandardParameterContext.Builder().id(CONTEXT_ID)
+ .name(CONTEXT_NAME)
.parameterReferenceManager(parameterReferenceManager)
.build());
- final ParameterContext inheritedContext = new
StandardParameterContext.Builder().id("inherited-id")
+ final ParameterContext inheritedContext = new
StandardParameterContext.Builder().id(INHERITED_CONTEXT_ID)
.parameterReferenceManager(parameterReferenceManager)
.name("Inherited")
.build();
@@ -106,14 +128,14 @@ public class TestStandardParameterContextDAO {
@Test
public void testVerifyUpdateInheritedProvidedParameter() {
final ParameterContextDTO dto = new ParameterContextDTO();
- dto.setId("id");
- dto.setName("Context");
+ dto.setId(CONTEXT_ID);
+ dto.setName(CONTEXT_NAME);
final List<ParameterContextReferenceEntity> refs = new ArrayList<>();
final ParameterContextReferenceEntity ref = new
ParameterContextReferenceEntity();
- ref.setId("inherited-id");
+ ref.setId(INHERITED_CONTEXT_ID);
ref.setComponent(new ParameterContextReferenceDTO());
- ref.getComponent().setId("inherited-id");
+ ref.getComponent().setId(INHERITED_CONTEXT_ID);
ref.getComponent().setName("Inherited");
refs.add(ref);
dto.setInheritedParameterContexts(refs);
@@ -125,8 +147,8 @@ public class TestStandardParameterContextDAO {
@Test
public void testVerifyUpdateNonInheritedProvidedParameter() {
final ParameterContextDTO dto = new ParameterContextDTO();
- dto.setId("id");
- dto.setName("Context");
+ dto.setId(CONTEXT_ID);
+ dto.setName(CONTEXT_NAME);
final Set<ParameterEntity> parameters = new HashSet<>();
final ParameterEntity parameter = new ParameterEntity();
parameter.setCanWrite(true);
@@ -141,9 +163,9 @@ public class TestStandardParameterContextDAO {
final List<ParameterContextReferenceEntity> refs = new ArrayList<>();
final ParameterContextReferenceEntity ref = new
ParameterContextReferenceEntity();
- ref.setId("inherited-id");
+ ref.setId(INHERITED_CONTEXT_ID);
ref.setComponent(new ParameterContextReferenceDTO());
- ref.getComponent().setId("inherited-id");
+ ref.getComponent().setId(INHERITED_CONTEXT_ID);
ref.getComponent().setName("Inherited");
refs.add(ref);
dto.setInheritedParameterContexts(refs);
@@ -151,4 +173,123 @@ public class TestStandardParameterContextDAO {
// Updating a provided parameter that is not inherited should fail
assertThrows(IllegalArgumentException.class, () ->
dao.verifyUpdate(dto, true));
}
+
+ @Test
+ public void testGetParametersNormalizesNullSourceForLocalParameter() {
+ final ParameterEntity parameterEntity =
createParameterEntity("param-null-source", "value-1", false, "description-1",
false, null, null);
+
+ final Map<String, Parameter> parameters =
dao.getParameters(createParameterContextDto(parameterEntity), null);
+
+ final Parameter parameter = parameters.get("param-null-source");
+ assertEquals("value-1", parameter.getValue());
+ assertEquals("description-1",
parameter.getDescriptor().getDescription());
+ assertFalse(parameter.getDescriptor().isSensitive());
+ assertFalse(parameter.isProvided());
+ assertNull(parameter.getParameterContextId());
+ }
+
+ @Test
+ public void
testGetParametersNormalizesCurrentSourceForExistingLocalParameterUpdate() {
+ final ParameterContext context = dao.getParameterContext(CONTEXT_ID);
+ context.setParameters(Map.of(
+ "param-current-source",
+ new
Parameter.Builder().name("param-current-source").value("existing-value").build()
+ ));
+
+ final ParameterEntity parameterEntity =
createParameterEntity("param-current-source", null, true, "description-2",
false, CONTEXT_ID, null);
+
+ final Map<String, Parameter> parameters =
dao.getParameters(createParameterContextDto(parameterEntity), context);
+
+ final Parameter parameter = parameters.get("param-current-source");
+ assertEquals("existing-value", parameter.getValue());
+ assertEquals("description-2",
parameter.getDescriptor().getDescription());
+ assertTrue(parameter.getDescriptor().isSensitive());
+ assertFalse(parameter.isProvided());
+ assertNull(parameter.getParameterContextId());
+ }
+
+ @Test
+ public void testGetParametersNormalizesForeignSourceAndPreservesFields() {
+ final ParameterEntity parameterEntity =
createParameterEntity("param-foreign-source", "value-3", true, "description-3",
false, "foreign-context", null);
+
+ final Map<String, Parameter> parameters =
dao.getParameters(createParameterContextDto(parameterEntity), null);
+
+ final Parameter parameter = parameters.get("param-foreign-source");
+ assertEquals("value-3", parameter.getValue());
+ assertEquals("description-3",
parameter.getDescriptor().getDescription());
+ assertTrue(parameter.getDescriptor().isSensitive());
+ assertFalse(parameter.isProvided());
+ assertNull(parameter.getParameterContextId());
+ }
+
+ @Test
+ public void testGetParametersNormalizesForeignSourceForProvidedParameter()
{
+ final ParameterEntity parameterEntity =
createParameterEntity("provided-param", "provided-value", false,
"provided-description", true, "foreign-context", null);
+
+ final Map<String, Parameter> parameters =
dao.getParameters(createParameterContextDto(parameterEntity), null);
+
+ final Parameter parameter = parameters.get("provided-param");
+ assertEquals("provided-value", parameter.getValue());
+ assertEquals("provided-description",
parameter.getDescriptor().getDescription());
+ assertFalse(parameter.getDescriptor().isSensitive());
+ assertTrue(parameter.isProvided());
+ assertNull(parameter.getParameterContextId());
+ }
+
+ @Test
+ public void
testGetParametersNormalizesForeignSourceForAssetBackedParameterAndPassesOwnershipValidation()
{
+ final Asset asset = mock(Asset.class);
+ when(asset.getOwnerIdentifier()).thenReturn(CONTEXT_ID);
+ when(asset.getFile()).thenReturn(new java.io.File("asset.bin"));
+ when(assetManager.getAsset("asset-1")).thenReturn(Optional.of(asset));
+
+ final AssetReferenceDTO assetReference = new AssetReferenceDTO();
+ assetReference.setId("asset-1");
+
+ final ParameterEntity parameterEntity =
createParameterEntity("asset-param", "client-value-ignored", false,
"asset-description", false,
+ "foreign-context", List.of(assetReference));
+ final ParameterContextDTO parameterContextDto =
createParameterContextDto(parameterEntity);
+
+ final Map<String, Parameter> parameters =
dao.getParameters(parameterContextDto, null);
+ final Parameter parameter = parameters.get("asset-param");
+
+ assertEquals(asset.getFile().getAbsolutePath(), parameter.getValue());
+ assertEquals("asset-description",
parameter.getDescriptor().getDescription());
+ assertNull(parameter.getParameterContextId());
+ assertEquals(Collections.singletonList(asset),
parameter.getReferencedAssets());
+ assertDoesNotThrow(() -> dao.verifyAssets(parameterContextDto,
parameters));
+ verify(assetManager).getAsset(eq("asset-1"));
+ }
+
+ private ParameterContextDTO createParameterContextDto(final
ParameterEntity... parameterEntities) {
+ final ParameterContextDTO parameterContextDto = new
ParameterContextDTO();
+ parameterContextDto.setId(CONTEXT_ID);
+ parameterContextDto.setName(CONTEXT_NAME);
+ parameterContextDto.setParameters(new
HashSet<>(List.of(parameterEntities)));
+ return parameterContextDto;
+ }
+
+ private ParameterEntity createParameterEntity(final String name, final
String value, final Boolean sensitive, final String description,
+ final Boolean provided,
final String sourceContextId, final List<AssetReferenceDTO> referencedAssets) {
+ final ParameterDTO parameterDto = new ParameterDTO();
+ parameterDto.setName(name);
+ parameterDto.setValue(value);
+ parameterDto.setSensitive(sensitive);
+ parameterDto.setDescription(description);
+ parameterDto.setProvided(provided);
+ parameterDto.setReferencedAssets(referencedAssets);
+ if (sourceContextId != null) {
+ final ParameterContextReferenceEntity parameterContextReference =
new ParameterContextReferenceEntity();
+ parameterContextReference.setId(sourceContextId);
+ final ParameterContextReferenceDTO parameterContextReferenceDto =
new ParameterContextReferenceDTO();
+ parameterContextReferenceDto.setId(sourceContextId);
+
parameterContextReference.setComponent(parameterContextReferenceDto);
+ parameterDto.setParameterContext(parameterContextReference);
+ }
+
+ final ParameterEntity parameterEntity = new ParameterEntity();
+ parameterEntity.setCanWrite(true);
+ parameterEntity.setParameter(parameterDto);
+ return parameterEntity;
+ }
}
diff --git
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
index 78b7e6e129e..e68c3dc71ca 100644
---
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
+++
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
@@ -33,6 +33,7 @@ import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextUpdateRequestEntity;
+import org.apache.nifi.web.api.entity.ParameterContextsEntity;
import org.apache.nifi.web.api.entity.ParameterEntity;
import org.apache.nifi.web.api.entity.ParameterGroupConfigurationEntity;
import
org.apache.nifi.web.api.entity.ParameterProviderApplyParametersRequestEntity;
@@ -1168,6 +1169,38 @@ public class ParameterContextIT extends NiFiSystemIT {
final ParameterContextEntity updatedParent =
getNifiClient().getParamContextClient().getParamContext(parentContext.getId(),
false);
assertEquals(1,
updatedParent.getComponent().getInheritedParameterContexts().size());
assertEquals(childContext2.getId(),
updatedParent.getComponent().getInheritedParameterContexts().get(0).getId());
+
+ final ParameterContextEntity deletedChildContext =
getNifiClient().getParamContextClient().getParamContext(childContext1.getId(),
false);
+
getNifiClient().getParamContextClient().deleteParamContext(childContext1.getId(),
String.valueOf(deletedChildContext.getRevision().getVersion()));
+
+ final ParameterContextsEntity parameterContexts =
getNifiClient().getParamContextClient().getParamContexts();
+ final ParameterContextEntity listedParent =
parameterContexts.getParameterContexts().stream()
+ .filter(context ->
parentContext.getId().equals(context.getId()))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(listedParent);
+
assertTrue(parameterContexts.getParameterContexts().stream().anyMatch(context
-> childContext2.getId().equals(context.getId())));
+
assertFalse(parameterContexts.getParameterContexts().stream().anyMatch(context
-> childContext1.getId().equals(context.getId())));
+ assertEquals(1,
listedParent.getComponent().getInheritedParameterContexts().size());
+ assertEquals(childContext2.getId(),
listedParent.getComponent().getInheritedParameterContexts().getFirst().getId());
+
+ final ParameterContextEntity survivingParentLocal =
getNifiClient().getParamContextClient().getParamContext(parentContext.getId(),
false);
+ assertTrue(survivingParentLocal.getComponent().getParameters().stream()
+ .noneMatch(parameter ->
"fileToIngest".equals(parameter.getParameter().getName())));
+
+ final ParameterContextEntity survivingParent =
getNifiClient().getParamContextClient().getParamContext(parentContext.getId(),
true);
+ assertEquals(1,
survivingParent.getComponent().getInheritedParameterContexts().size());
+ assertEquals(childContext2.getId(),
survivingParent.getComponent().getInheritedParameterContexts().getFirst().getId());
+ final ParameterDTO survivingParameter =
survivingParent.getComponent().getParameters().stream()
+ .map(ParameterEntity::getParameter)
+ .filter(parameter -> "otherParam".equals(parameter.getName()))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(survivingParameter);
+ assertTrue(survivingParameter.getInherited());
+ assertEquals(childContext2.getId(),
survivingParameter.getParameterContext().getId());
+ assertTrue(survivingParent.getComponent().getParameters().stream()
+ .noneMatch(parameter ->
"fileToIngest".equals(parameter.getParameter().getName())));
}
@Test