From be57a210ffb2e3e3eefccabfe7100edaf2e9a43b Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Wed, 29 Sep 2021 17:04:35 -0400 Subject: [PATCH] NIFI-8491: Adding support for configuring parameter context inheritance (#5371) * NIFI-8491: - Adding support for configuring parameter context inheritance. * NIFI-8491: - Allowing changes to the parameter context inheritance to drive Apply disabled state. * NIFI-8491: Updating StandardParameterContext#isAuthorized check * NIFI-8491: - Showing selected inherited parameter contexts in ready only form when appropriate. - Allowing available parameter contexts to be inherited by double clicking. - Removing support for rendering unauthorized inherited parameter contexts as they can no longer be opened. * NIFI-8491: Adding inherited param context verification earlier * NIFI-8491: - Addressing CI failures by rolling back to some order JS language spec to allow yui-compress to minify and compress. * NIFI-8491: - Ensuring selected context sort order is honored. - Ensuring the Apply button is correctly enabled. - Showing Pending Apply message when selected Parameter Context changes. - Ensuring the Parameter's tab is selected now that there is a third tab. * Updates to inherited param context verification * Improving validation between parameters/inherited parameters * NIFI-8491: - Ensuring the available parameter contexts are loaded whether the edit dialog is opened from the listing or outside of the listing. * NIFI-8491: - Fixing conditions we check if the parameter context listing is currently open. * NIFI-8491: - Waiting for the parameter contexts to load prior to rendering the parameter context inheritance tab and showing the dialog. * NIFI-8491: - Fixing pending apply message clipping. - Hiding pending apply message after clicking Apply. Co-authored-by: Joe Gresock This closes #5371 --- .../apache/nifi/web/api/dto/ParameterDTO.java | 10 + .../parameter/StandardParameterContext.java | 115 ++++- .../nifi/parameter/ParameterContext.java | 19 +- .../nifi/web/StandardNiFiServiceFacade.java | 51 +- .../nifi/web/api/dto/EntityFactory.java | 2 +- .../nifi/web/dao/ParameterContextDAO.java | 18 + .../dao/impl/StandardParameterContextDAO.java | 90 +--- .../canvas/new-parameter-context-dialog.jsp | 47 +- .../src/main/webapp/css/dialog.css | 1 + .../css/new-parameter-context-dialog.css | 80 ++- .../js/nf/canvas/nf-parameter-contexts.js | 462 +++++++++++++++--- 11 files changed, 700 insertions(+), 195 deletions(-) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java index 12a0262484..86701b5df4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java @@ -32,6 +32,7 @@ public class ParameterDTO { private Boolean valueRemoved; private Set referencingComponents; private ParameterContextReferenceEntity parameterContext; + private Boolean inherited; @ApiModelProperty("The name of the Parameter") public String getName() { @@ -42,6 +43,15 @@ public class ParameterDTO { this.name = name; } + @ApiModelProperty(value = "Whether or not the Parameter is inherited from another context", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + public Boolean getInherited() { + return inherited; + } + + public void setInherited(final Boolean inherited) { + this.inherited = inherited; + } + @ApiModelProperty("The description of the Parameter") public String getDescription() { return description; 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 e311770e8d..51e56640dd 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 @@ -35,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -123,7 +124,7 @@ public class StandardParameterContext implements ParameterContext { final Map effectiveParameterUpdates = getEffectiveParameterUpdates(currentEffectiveParameters, effectiveProposedParameters); - verifyCanSetParameters(effectiveParameterUpdates); + verifyCanSetParameters(effectiveParameterUpdates, true); // Update the actual parameters updateParameters(parameters, updatedParameters, true); @@ -339,6 +340,17 @@ public class StandardParameterContext implements ParameterContext { } } + @Override + public Map getEffectiveParameterUpdates(final Map parameterUpdates, final List inheritedParameterContexts) { + Objects.requireNonNull(parameterUpdates, "Parameter Updates must be specified"); + Objects.requireNonNull(inheritedParameterContexts, "Inherited parameter contexts must be specified"); + + final Map currentEffectiveParameters = getEffectiveParameters(); + final Map effectiveProposedParameters = getEffectiveParameters(inheritedParameterContexts, getProposedParameters(parameterUpdates), new HashMap<>()); + + return getEffectiveParameterUpdates(currentEffectiveParameters, effectiveProposedParameters); + } + /** * Constructs an effective view of the parameters, including nested parameters, assuming the given map of parameters. * This allows an inspection of what parameters would be available if the given parameters were set in this ParameterContext. @@ -373,15 +385,23 @@ public class StandardParameterContext implements ParameterContext { // Loop backwards so that the first ParameterContext in the list will override any parameters later in the list for(int i = parameterContexts.size() - 1; i >= 0; i--) { ParameterContext parameterContext = parameterContexts.get(i); - allOverrides.putAll(overrideParameters(effectiveParameters, parameterContext.getEffectiveParameters(), parameterContext)); + combineOverrides(allOverrides, overrideParameters(effectiveParameters, parameterContext.getEffectiveParameters(), parameterContext)); } // Finally, override all child parameters with our own - allOverrides.putAll(overrideParameters(effectiveParameters, proposedParameters, this)); + combineOverrides(allOverrides, overrideParameters(effectiveParameters, proposedParameters, this)); return effectiveParameters; } + private void combineOverrides(final Map> existingOverrides, final Map> newOverrides) { + for (final Map.Entry> entry : newOverrides.entrySet()) { + final ParameterDescriptor key = entry.getKey(); + final List existingOverrideList = existingOverrides.computeIfAbsent(key, k -> new ArrayList<>()); + existingOverrideList.addAll(entry.getValue()); + } + } + private Map> overrideParameters(final Map existingParameters, final Map overridingParameters, final ParameterContext overridingContext) { @@ -456,32 +476,49 @@ public class StandardParameterContext implements ParameterContext { } } + @Override + public void verifyCanUpdateParameterContext(final Map parameterUpdates, final List inheritedParameterContexts) { + verifyCanUpdateParameterContext(parameterUpdates, inheritedParameterContexts, false); + } + + private void verifyCanUpdateParameterContext(final Map parameterUpdates, final List inheritedParameterContexts, final boolean duringUpdate) { + if (inheritedParameterContexts == null) { + return; + } + verifyNoCycles(inheritedParameterContexts); + + final Map currentEffectiveParameters = getEffectiveParameters(); + final Map effectiveProposedParameters = getEffectiveParameters(inheritedParameterContexts, getProposedParameters(parameterUpdates), new HashMap<>()); + final Map effectiveParameterUpdates = getEffectiveParameterUpdates(currentEffectiveParameters, effectiveProposedParameters); + + try { + verifyCanSetParameters(currentEffectiveParameters, effectiveParameterUpdates, duringUpdate); + } catch (final IllegalStateException e) { + // Wrap with a more accurate message + throw new IllegalStateException(String.format("Could not update inherited Parameter Contexts for Parameter Context [%s] because: %s", + name, e.getMessage()), e); + } + } + @Override public void setInheritedParameterContexts(final List inheritedParameterContexts) { - if (inheritedParameterContexts.equals(this.inheritedParameterContexts)) { + if (inheritedParameterContexts == null || inheritedParameterContexts.equals(this.inheritedParameterContexts)) { // No changes return; } + verifyCanUpdateParameterContext(Collections.emptyMap(), inheritedParameterContexts, true); + final Map parameterUpdates = new HashMap<>(); writeLock.lock(); try { this.version++; - verifyNoCycles(inheritedParameterContexts); final Map currentEffectiveParameters = getEffectiveParameters(); final Map effectiveProposedParameters = getEffectiveParameters(inheritedParameterContexts); final Map effectiveParameterUpdates = getEffectiveParameterUpdates(currentEffectiveParameters, effectiveProposedParameters); - try { - verifyCanSetParameters(currentEffectiveParameters, effectiveParameterUpdates); - } catch (final IllegalStateException e) { - // Wrap with a more accurate message - throw new IllegalStateException(String.format("Could not update inherited Parameter Contexts for Parameter Context [%s] because: %s", - name, e.getMessage()), e); - } - this.inheritedParameterContexts.clear(); this.inheritedParameterContexts.addAll(inheritedParameterContexts); @@ -570,17 +607,30 @@ public class StandardParameterContext implements ParameterContext { @Override public void verifyCanSetParameters(final Map updatedParameters) { - verifyCanSetParameters(parameters, updatedParameters); + verifyCanSetParameters(updatedParameters, false); } - public void verifyCanSetParameters(final Map currentParameters, final Map updatedParameters) { + /** + * Ensures that it is legal to update the Parameters for this Parameter Context to match the given set of Parameters + * @param updatedParameters the updated set of parameters, keyed by Parameter name + * @param duringUpdate If true, this check will be treated as if a ParameterContext update is imminent, meaning + * referencing components may not be active even for updated values. If false, a parameter + * value update will be valid even if referencing components are active because it will be + * assumed that these will be stopped prior to the actual update. + * @throws IllegalStateException if setting the given set of Parameters is not legal + */ + public void verifyCanSetParameters(final Map updatedParameters, final boolean duringUpdate) { + verifyCanSetParameters(parameters, updatedParameters, duringUpdate); + } + + public void verifyCanSetParameters(final Map currentParameters, final Map updatedParameters, final boolean duringUpdate) { // Ensure that the updated parameters will not result in changing the sensitivity flag of any parameter. for (final Map.Entry entry : updatedParameters.entrySet()) { final String parameterName = entry.getKey(); final Parameter parameter = entry.getValue(); if (parameter == null) { // parameter is being deleted. - validateReferencingComponents(parameterName, null,"remove"); + validateReferencingComponents(parameterName, null, duringUpdate); continue; } @@ -589,7 +639,7 @@ public class StandardParameterContext implements ParameterContext { } validateSensitiveFlag(currentParameters, parameter); - validateReferencingComponents(parameterName, parameter, "update"); + validateReferencingComponents(parameterName, parameter, duringUpdate); } } @@ -611,11 +661,12 @@ public class StandardParameterContext implements ParameterContext { } } - - private void validateReferencingComponents(final String parameterName, final Parameter parameter, final String parameterAction) { + private void validateReferencingComponents(final String parameterName, final Parameter parameter, final boolean duringUpdate) { + final boolean isDeletion = (parameter == null); + final String action = isDeletion ? "remove" : "update"; for (final ProcessorNode procNode : parameterReferenceManager.getProcessorsReferencing(this, parameterName)) { - if (procNode.isRunning()) { - throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + parameterName + "' because it is referenced by " + procNode + ", which is currently running"); + if (procNode.isRunning() && (isDeletion || duringUpdate)) { + throw new IllegalStateException("Cannot " + action + " parameter '" + parameterName + "' because it is referenced by " + procNode + ", which is currently running"); } if (parameter != null) { @@ -625,9 +676,9 @@ public class StandardParameterContext implements ParameterContext { for (final ControllerServiceNode serviceNode : parameterReferenceManager.getControllerServicesReferencing(this, parameterName)) { final ControllerServiceState serviceState = serviceNode.getState(); - if (serviceState != ControllerServiceState.DISABLED) { - throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + parameterName + "' because it is referenced by " - + serviceNode + ", which currently has a state of " + serviceState); + if (serviceState != ControllerServiceState.DISABLED && (isDeletion || duringUpdate)) { + throw new IllegalStateException("Cannot " + action + " parameter '" + parameterName + "' because it is referenced by " + + serviceNode + ", which currently has a state of " + serviceState); } if (parameter != null) { @@ -678,6 +729,22 @@ public class StandardParameterContext implements ParameterContext { } } + @Override + public boolean isAuthorized(final Authorizer authorizer, final RequestAction action, final NiFiUser user) { + boolean isAuthorized = ParameterContext.super.isAuthorized(authorizer, action, user); + + if (RequestAction.READ == action) { + for (final ParameterContext parameterContext : inheritedParameterContexts) { + isAuthorized &= parameterContext.isAuthorized(authorizer, action, user); + if (!isAuthorized) { + break; + } + } + } + + return isAuthorized; + } + @Override public Authorizable getParentAuthorizable() { return new Authorizable() { 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 670c9ce533..62641a607d 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 @@ -109,12 +109,29 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable */ Map getEffectiveParameters(); + /** + * Returns the resulting map of effective parameter updates if the given parameter updates and inherited parameter contexts were to be applied. + * This allows potential changes to be detected before actually applying the parameter updates. + * @param parameterUpdates A map from parameter name to updated parameter (null if removal is desired) + * @param inheritedParameterContexts An ordered list of parameter contexts to inherit from + * @return The effective map of parameter updates that would result if these changes were applied. This includes only parameters that would + * be effectively updated or removed, and is mapped by parameter name + */ + Map getEffectiveParameterUpdates(Map parameterUpdates, List inheritedParameterContexts); + /** * Returns the ParameterReferenceManager that is associated with this ParameterContext * @return the ParameterReferenceManager that is associated with this ParameterContext */ ParameterReferenceManager getParameterReferenceManager(); + /** + * Verifies whether the parameter context can be updated with the provided parameters and inherited parameter contexts. + * @param parameterUpdates A map from parameter name to updated parameter (null if removal is desired) + * @param inheritedParameterContexts the list of ParameterContexts from which to inherit parameters + */ + void verifyCanUpdateParameterContext(Map parameterUpdates, List inheritedParameterContexts); + /** * Updates the ParameterContexts within this context to match the given list of ParameterContexts. All parameter in these * ParameterContexts are inherited by this ParameterContext, and can be referenced as if they were actually in this ParameterContext. @@ -125,7 +142,7 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable * * @param inheritedParameterContexts the list of ParameterContexts from which to inherit parameters, in priority order first to last * @throws IllegalStateException if the list of ParameterContexts is invalid (in case of a circular reference or - * in case {@link #verifyCanSetParameters(Map)} verifyCanSetParameters} would throw an exception) + * in case {@link #verifyCanSetParameters(Map) verifyCanSetParameters} would throw an exception) */ void setInheritedParameterContexts(List inheritedParameterContexts); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index cfe7941ef9..4d31a11a9f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -91,6 +91,14 @@ import org.apache.nifi.controller.status.ProcessGroupStatus; import org.apache.nifi.controller.status.ProcessorStatus; import org.apache.nifi.diagnostics.SystemDiagnostics; import org.apache.nifi.events.BulletinFactory; +import org.apache.nifi.flow.VersionedComponent; +import org.apache.nifi.flow.VersionedConfigurableComponent; +import org.apache.nifi.flow.VersionedConnection; +import org.apache.nifi.flow.VersionedControllerService; +import org.apache.nifi.flow.VersionedFlowCoordinates; +import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedProcessor; +import org.apache.nifi.flow.VersionedPropertyDescriptor; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.ProcessGroupCounts; import org.apache.nifi.groups.RemoteProcessGroup; @@ -119,19 +127,11 @@ import org.apache.nifi.registry.flow.FlowRegistry; import org.apache.nifi.registry.flow.FlowRegistryClient; import org.apache.nifi.registry.flow.RestBasedFlowRegistry; import org.apache.nifi.registry.flow.VersionControlInformation; -import org.apache.nifi.flow.VersionedComponent; -import org.apache.nifi.flow.VersionedConfigurableComponent; -import org.apache.nifi.flow.VersionedConnection; -import org.apache.nifi.flow.VersionedControllerService; import org.apache.nifi.registry.flow.VersionedFlow; -import org.apache.nifi.flow.VersionedFlowCoordinates; import org.apache.nifi.registry.flow.VersionedFlowSnapshot; import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata; import org.apache.nifi.registry.flow.VersionedFlowState; import org.apache.nifi.registry.flow.VersionedParameterContext; -import org.apache.nifi.flow.VersionedProcessGroup; -import org.apache.nifi.flow.VersionedProcessor; -import org.apache.nifi.flow.VersionedPropertyDescriptor; import org.apache.nifi.registry.flow.diff.ComparableDataFlow; import org.apache.nifi.registry.flow.diff.ConciseEvolvingDifferenceDescriptor; import org.apache.nifi.registry.flow.diff.DifferenceType; @@ -1315,6 +1315,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { group -> group.getParameterContext() != null && (group.getParameterContext().getIdentifier().equals(parameterContextDto.getId()) || group.getParameterContext().inheritsFrom(parameterContext.getIdentifier()))); + setEffectiveParameterUpdates(parameterContextDto); + final Set updatedParameterNames = getUpdatedParameterNames(parameterContextDto); // Clear set of Affected Components for each Parameter. This parameter is read-only and it will be populated below. @@ -1371,6 +1373,39 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { return dtoFactory.createAffectedComponentEntities(affectedComponents, revisionManager); } + private void setEffectiveParameterUpdates(final ParameterContextDTO parameterContextDto) { + final ParameterContext parameterContext = parameterContextDAO.getParameterContext(parameterContextDto.getId()); + + final Map parameterUpdates = parameterContextDAO.getParameters(parameterContextDto, parameterContext); + final List inheritedParameterContexts = parameterContextDAO.getInheritedParameterContexts(parameterContextDto); + final Map proposedParameterUpdates = parameterContext.getEffectiveParameterUpdates(parameterUpdates, inheritedParameterContexts); + final Map parameterEntities = parameterContextDto.getParameters().stream() + .collect(Collectors.toMap(entity -> entity.getParameter().getName(), Function.identity())); + parameterContextDto.getParameters().clear(); + + for (final Map.Entry entry : proposedParameterUpdates.entrySet()) { + final String parameterName = entry.getKey(); + final Parameter parameter = entry.getValue(); + final ParameterEntity parameterEntity; + if (parameterEntities.containsKey(parameterName)) { + parameterEntity = parameterEntities.get(parameterName); + } else if (parameter == null) { + parameterEntity = new ParameterEntity(); + final ParameterDTO parameterDTO = new ParameterDTO(); + parameterDTO.setName(parameterName); + parameterEntity.setParameter(parameterDTO); + } else { + parameterEntity = dtoFactory.createParameterEntity(parameterContext, parameter, revisionManager, parameterContextDAO); + } + + // Parameter is inherited if either this is the removal of a parameter not directly in this context, or it's parameter not specified directly in the DTO + final boolean isInherited = (parameter == null && !parameterContext.getParameters().containsKey(new ParameterDescriptor.Builder().name(parameterName).build())) + || (parameter != null && !parameterEntities.containsKey(parameterName)); + parameterEntity.getParameter().setInherited(isInherited); + parameterContextDto.getParameters().add(parameterEntity); + } + } + private void addReferencingComponents(final ControllerServiceNode service, final Set affectedComponents, final List affectedParameterDtos, final boolean includeInactive) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java index 1d6d8bbba8..9d5ad9eb21 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java @@ -575,7 +575,7 @@ public final class EntityFactory { final ParameterContextEntity entity = new ParameterContextEntity(); entity.setRevision(revision); if (dto != null) { - entity.setPermissions(permissions);; + entity.setPermissions(permissions); entity.setId(dto.getId()); if (permissions != null && permissions.getCanRead()) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ParameterContextDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ParameterContextDAO.java index 07daab63e4..51f6d969ff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ParameterContextDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ParameterContextDAO.java @@ -16,10 +16,13 @@ */ package org.apache.nifi.web.dao; +import org.apache.nifi.parameter.Parameter; import org.apache.nifi.parameter.ParameterContext; import org.apache.nifi.parameter.ParameterContextLookup; import org.apache.nifi.web.api.dto.ParameterContextDTO; +import java.util.List; +import java.util.Map; import java.util.Set; public interface ParameterContextDAO extends ParameterContextLookup { @@ -39,6 +42,14 @@ public interface ParameterContextDAO extends ParameterContextLookup { */ ParameterContext createParameterContext(ParameterContextDTO parameterContextDto); + /** + * Returns a map from parameter name to intended parameter, given the DTO. + * @param parameterContextDto A parameter context DTO containing parameter updates + * @param context The existing parameter context + * @return The resulting parameter map containing updated parameters (or removals) + */ + Map getParameters(ParameterContextDTO parameterContextDto, ParameterContext context); + /** * Gets all of the parameter contexts. * @@ -54,6 +65,13 @@ public interface ParameterContextDAO extends ParameterContextLookup { */ ParameterContext updateParameterContext(ParameterContextDTO parameterContextDto); + /** + * Returns a list of the inherited parameter contexts proposed by the DTO. + * @param parameterContextDto The parameter context DTO + * @return a list of the inherited parameter contexts proposed by the DTO + */ + List getInheritedParameterContexts(ParameterContextDTO parameterContextDto); + /** * Determines whether this parameter context can be updated. * 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 1c8c3a2fcd..d4a69ac772 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 @@ -20,11 +20,8 @@ import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.controller.ComponentNode; import org.apache.nifi.controller.FlowController; import org.apache.nifi.controller.ProcessorNode; -import org.apache.nifi.controller.PropertyConfiguration; import org.apache.nifi.controller.flow.FlowManager; import org.apache.nifi.controller.service.ControllerServiceNode; import org.apache.nifi.controller.service.ControllerServiceState; @@ -32,8 +29,6 @@ import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.parameter.Parameter; import org.apache.nifi.parameter.ParameterContext; import org.apache.nifi.parameter.ParameterDescriptor; -import org.apache.nifi.parameter.ParameterReference; -import org.apache.nifi.parameter.ParameterReferenceManager; import org.apache.nifi.web.ResourceNotFoundException; import org.apache.nifi.web.api.dto.ParameterContextDTO; import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO; @@ -73,7 +68,7 @@ public class StandardParameterContextDAO implements ParameterContextDAO { if (inheritedParameterContexts != null) { resolveInheritedParameterContexts(parameterContextDto); // This will throw an exception if one is not found - inheritedParameterContexts.stream().forEach(entity -> flowManager.getParameterContextManager() + inheritedParameterContexts.forEach(entity -> flowManager.getParameterContextManager() .getParameterContext(entity.getComponent().getId())); } authorizeReferences(parameterContextDto); @@ -136,7 +131,8 @@ public class StandardParameterContextDAO implements ParameterContextDAO { } } - private Map getParameters(final ParameterContextDTO parameterContextDto, final ParameterContext context) { + @Override + public Map getParameters(final ParameterContextDTO parameterContextDto, final ParameterContext context) { final Set parameterEntities = parameterContextDto.getParameters(); if (parameterEntities == null) { return Collections.emptyMap(); @@ -146,6 +142,11 @@ public class StandardParameterContextDAO implements ParameterContextDAO { for (final ParameterEntity parameterEntity : parameterEntities) { final ParameterDTO parameterDto = parameterEntity.getParameter(); + // Inherited parameters are only included for referencing components, but we should not save them as direct parameters + if (parameterDto.getInherited() != null && parameterDto.getInherited()) { + continue; + } + if (parameterDto.getName() == null) { throw new IllegalArgumentException("Cannot specify a Parameter without a name"); } @@ -226,7 +227,8 @@ public class StandardParameterContextDAO implements ParameterContextDAO { return context; } - private List getInheritedParameterContexts(final ParameterContextDTO parameterContextDto) { + @Override + public List getInheritedParameterContexts(final ParameterContextDTO parameterContextDto) { resolveInheritedParameterContexts(parameterContextDto); final List inheritedParameterContexts = new ArrayList<>(); @@ -243,76 +245,10 @@ public class StandardParameterContextDAO implements ParameterContextDAO { verifyInheritedParameterContextRefs(parameterContextDto); final ParameterContext currentContext = getParameterContext(parameterContextDto.getId()); - for (final ParameterEntity parameterEntity : parameterContextDto.getParameters()) { - final ParameterDTO parameterDto = parameterEntity.getParameter(); - final String parameterName = parameterDto.getName(); - final ParameterReferenceManager referenceManager = currentContext.getParameterReferenceManager(); - for (final ProcessorNode processor : referenceManager.getProcessorsReferencing(currentContext, parameterName)) { - verifyParameterUpdate(parameterDto, processor, currentContext.getName(), verifyComponentStates, processor.isRunning(), "Processor that is running"); - } - - final Set referencingServices = referenceManager.getControllerServicesReferencing(currentContext, parameterName); - for (final ControllerServiceNode serviceNode : referencingServices) { - final ControllerServiceState serviceState = serviceNode.getState(); - final boolean serviceActive = serviceState != ControllerServiceState.DISABLED; - - verifyParameterUpdate(parameterDto, serviceNode, currentContext.getName(), verifyComponentStates, serviceActive, - "Controller Service [id=" + serviceNode.getIdentifier() + "] with a state of " + serviceState + " (state expected to be DISABLED)"); - } - } - } - - private void verifyParameterUpdate(final ParameterDTO parameterDto, final ComponentNode component, final String contextName, - final boolean verifyComponentStates, final boolean active, final String activeExplanation) { - - final String parameterName = parameterDto.getName(); - final Boolean parameterSensitive = parameterDto.getSensitive(); - final boolean parameterDeletion = parameterDto.getDescription() == null && parameterDto.getSensitive() == null && parameterDto.getValue() == null; - - // For any parameter that is added or modified, we need to ensure that the new configuration will not result in a Sensitive Parameter being referenced by a non-Sensitive Property - // or a Non-Sensitive Parameter being referenced by a Sensitive Property. - // Additionally, if 'verifyComponentStates' or parameter is being deleted, we must ensure that any component that references a value that is to be updated - // is stopped (if a processor) or disabled (if a controller service). - for (final Map.Entry entry : component.getProperties().entrySet()) { - final PropertyConfiguration configuration = entry.getValue(); - if (configuration == null) { - continue; - } - - for (final ParameterReference reference : configuration.getParameterReferences()) { - final String referencedParameterName = reference.getParameterName(); - if (referencedParameterName.equals(parameterName)) { - if (entry.getKey().isSensitive() && !parameterDeletion && !Boolean.TRUE.equals(parameterSensitive)) { - throw new IllegalStateException("Cannot update Parameter Context " + contextName + " because the update would add a Non-Sensitive Parameter " + - "named '" + parameterName + "' but this Parameter already is referenced by a Sensitive Property."); - } - - if (!entry.getKey().isSensitive() && !parameterDeletion && Boolean.TRUE.equals(parameterSensitive)) { - throw new IllegalStateException("Cannot update Parameter Context " + contextName + " because the update would add a Sensitive Parameter named " + - "'" + parameterName + "' but this Parameter already is referenced by a Non-Sensitive Property."); - } - - if (active && (verifyComponentStates || parameterDeletion)) { - if (parameterDeletion) { - // First check if the actual parameter context is now missing the parameter: it may not be, - // if the parameter is inherited from another context - final ProcessGroup processGroup = flowManager.getGroup(component.getProcessGroupIdentifier()); - final ParameterContext parameterContext = processGroup.getParameterContext(); - final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder() - .name(parameterName).build(); - if (!parameterContext.hasEffectiveValueIfRemoved(parameterDescriptor)) { - throw new IllegalStateException("Cannot update Parameter Context " + contextName + " because the " + parameterName + " Parameter is being referenced by a " + - activeExplanation + "."); - } - } else { - throw new IllegalStateException("Cannot update Parameter Context " + contextName + " because it has Parameters that are being referenced by a " + - activeExplanation + "."); - } - } - } - } - } + final List inheritedParameterContexts = getInheritedParameterContexts(parameterContextDto); + final Map parameters = parameterContextDto.getParameters() == null ? Collections.emptyMap() : getParameters(parameterContextDto, currentContext); + currentContext.verifyCanUpdateParameterContext(parameters, inheritedParameterContexts); } /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp index d1e10a5d92..c788921fdf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp @@ -20,7 +20,7 @@
-
+
-
+
@@ -96,8 +96,51 @@
+
+
+
+
+
+ Available Parameter Contexts +
+
+
+
    +
    +
    +
    +
     
    +
    +
    +
    + Selected Parameter Context +
    +
    +
    +
      +
      +
      +
      +
      + +
      +