From 2609fd5f558e3b39a77c70d10e7a81c07022cad3 Mon Sep 17 00:00:00 2001 From: Joe Gresock Date: Wed, 1 Feb 2023 10:30:23 -0500 Subject: [PATCH] NIFI-11122 Corrected provided parameter context inheritance after creation This closes #6913 Signed-off-by: David Handermann --- .../StandardParameterProviderNode.java | 17 +- .../parameter/StandardParameterContext.java | 6 +- .../nifi/parameter/ParameterContext.java | 6 + .../dao/impl/StandardParameterContextDAO.java | 3 +- .../impl/TestStandardParameterContextDAO.java | 157 ++++++++++++++++++ 5 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java index aca97a9599..c391e30ade 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java @@ -348,7 +348,22 @@ public class StandardParameterProviderNode extends AbstractComponentNode impleme if (groupConfiguration == null) { continue; } - reference.verifyCanSetParameters(getFetchedParameterUpdateMap(reference, groupConfiguration)); + final Map parameterUpdates = getFetchedParameterUpdateMap(reference, groupConfiguration); + final Collection removedParametersWithReferences = new HashSet<>(); + for (final Map.Entry entry : parameterUpdates.entrySet()) { + final String parameterName = entry.getKey(); + if (entry.getValue() == null) { + reference.getParameter(parameterName).ifPresent(currentParameter -> { + if (reference.hasReferencingComponents(currentParameter)) { + removedParametersWithReferences.add(parameterName); + } + }); + } + } + // Any deletions of parameters with referencing components should be removed from consideration, + // since we do not remove provided parameters that are deleted until they are no longer referenced + removedParametersWithReferences.forEach(parameterUpdates::remove); + reference.verifyCanSetParameters(parameterUpdates); } } finally { readLock.unlock(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java index 339317fdd9..da3350f099 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java @@ -640,7 +640,8 @@ public class StandardParameterContext implements ParameterContext { // If a current parameter is not in the proposed parameters, it was effectively removed if (!effectiveProposedParameters.containsKey(currentParameterDescriptor)) { final Parameter parameter = entry.getValue(); - if (parameter.isProvided() && hasReferencingComponents(parameter)) { + final boolean isParameterInherited = !parameter.getParameterContextId().equals(id); + if (!isParameterInherited && parameter.isProvided() && hasReferencingComponents(parameter)) { logger.info("Provided parameter [{}] was removed from the source, but it is referenced by a component, so the parameter will be preserved", currentParameterDescriptor.getName()); } else { @@ -651,7 +652,8 @@ public class StandardParameterContext implements ParameterContext { return effectiveParameterUpdates; } - private boolean hasReferencingComponents(final Parameter parameter) { + @Override + public boolean hasReferencingComponents(final Parameter parameter) { if (parameter == null) { return false; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java index aa13288d98..7177bbbed6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java @@ -174,6 +174,12 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable */ List getInheritedParameterContextNames(); + /** + * @param parameter A parameter + * @return True if the parameter has referencing components + */ + boolean hasReferencingComponents(Parameter parameter); + /** * Returns true if this ParameterContext inherits from the given parameter context, either * directly or indirectly. diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java index 125dff0d86..6504fd943d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java @@ -337,7 +337,8 @@ public class StandardParameterContextDAO implements ParameterContextDAO { } } else { final boolean hasProvidedParameters = parameters.stream() - .anyMatch(parameter -> parameter.getParameter().getProvided() != null && parameter.getParameter().getProvided()); + .anyMatch(parameter -> parameter.getParameter().getProvided() != null && parameter.getParameter().getProvided() + && !parameter.getParameter().getInherited()); if (hasProvidedParameters) { throw new IllegalArgumentException(String.format("Provided Parameters may not be set on Context [%s] because its " + "parameters can only be user-entered", parameterContextDTO.getName())); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java new file mode 100644 index 0000000000..361a68e535 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardParameterContextDAO.java @@ -0,0 +1,157 @@ +/* + * 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.web.dao.impl; + +import org.apache.nifi.authorization.AuthorizationRequest; +import org.apache.nifi.authorization.AuthorizationResult; +import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.user.NiFiUser; +import org.apache.nifi.authorization.user.NiFiUserDetails; +import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.controller.FlowController; +import org.apache.nifi.controller.flow.FlowManager; +import org.apache.nifi.parameter.Parameter; +import org.apache.nifi.parameter.ParameterContext; +import org.apache.nifi.parameter.ParameterDescriptor; +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.ParameterContextDTO; +import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO; +import org.apache.nifi.web.api.dto.ParameterDTO; +import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity; +import org.apache.nifi.web.api.entity.ParameterEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class TestStandardParameterContextDAO { + + private StandardParameterContextDAO dao; + + @Mock(answer = RETURNS_DEEP_STUBS) + private FlowController flowController; + + @Mock + private Authentication authentication; + + @Mock + private Authorizer authorizer; + + @BeforeEach + void setUp() { + dao = new StandardParameterContextDAO(); + dao.setFlowController(flowController); + dao.setAuthorizer(authorizer); + 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); + + final ParameterReferenceManager parameterReferenceManager = new StandardParameterReferenceManager(flowController.getFlowManager()); + + final FlowManager flowManager = flowController.getFlowManager(); + final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager(); + when(flowManager.getParameterContextManager()).thenReturn(parameterContextLookup); + parameterContextLookup.addParameterContext(new StandardParameterContext.Builder().id("id") + .name("Context") + .parameterReferenceManager(parameterReferenceManager) + .build()); + final ParameterContext inheritedContext = new StandardParameterContext.Builder().id("inherited-id") + .parameterReferenceManager(parameterReferenceManager) + .name("Inherited") + .build(); + final Map parameters = new HashMap<>(); + parameters.put("inherited-param", new Parameter(new ParameterDescriptor.Builder().name("inherited-param").build(), + "value", null, true)); + inheritedContext.setParameters(parameters); + parameterContextLookup.addParameterContext(inheritedContext); + } + + @Test + public void testVerifyUpdateInheritedProvidedParameter() { + final ParameterContextDTO dto = new ParameterContextDTO(); + dto.setId("id"); + dto.setName("Context"); + + final List refs = new ArrayList<>(); + final ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity(); + ref.setId("inherited-id"); + ref.setComponent(new ParameterContextReferenceDTO()); + ref.getComponent().setId("inherited-id"); + ref.getComponent().setName("Inherited"); + refs.add(ref); + dto.setInheritedParameterContexts(refs); + + // Updating a provided parameter that is inherited is allowed + dao.verifyUpdate(dto, true); + } + + @Test + public void testVerifyUpdateNonInheritedProvidedParameter() { + final ParameterContextDTO dto = new ParameterContextDTO(); + dto.setId("id"); + dto.setName("Context"); + final Set parameters = new HashSet<>(); + final ParameterEntity parameter = new ParameterEntity(); + parameter.setCanWrite(true); + final ParameterDTO parameterDto = new ParameterDTO(); + parameterDto.setProvided(true); + parameterDto.setName("param"); + parameterDto.setValue("value"); + parameterDto.setInherited(false); + parameter.setParameter(parameterDto); + parameters.add(parameter); + dto.setParameters(parameters); + + final List refs = new ArrayList<>(); + final ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity(); + ref.setId("inherited-id"); + ref.setComponent(new ParameterContextReferenceDTO()); + ref.getComponent().setId("inherited-id"); + ref.getComponent().setName("Inherited"); + refs.add(ref); + dto.setInheritedParameterContexts(refs); + + + // Updating a provided parameter that is not inherited should fail + assertThrows(IllegalArgumentException.class, () -> dao.verifyUpdate(dto, true)); + } +} \ No newline at end of file