NIFI-8490: Adding inherited parameter contexts (#5072)

- Allowing inherited param contexts on creation, updating PC authorization
This commit is contained in:
Joe Gresock 2021-08-30 10:44:31 -04:00 committed by GitHub
parent 60b08cc569
commit 4a3e81531b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2420 additions and 184 deletions

View File

@ -21,10 +21,20 @@ import java.util.Objects;
public class Parameter {
private final ParameterDescriptor descriptor;
private final String value;
private final String parameterContextId;
public Parameter(final ParameterDescriptor descriptor, final String value) {
private Parameter(final ParameterDescriptor descriptor, final String value, final String parameterContextId) {
this.descriptor = descriptor;
this.value = value;
this.parameterContextId = parameterContextId;
}
public Parameter(final Parameter parameter, final String parameterContextId) {
this(parameter.getDescriptor(), parameter.getValue(), parameterContextId);
}
public Parameter(final ParameterDescriptor descriptor, final String value) {
this(descriptor, value, null);
}
public ParameterDescriptor getDescriptor() {
@ -35,6 +45,10 @@ public class Parameter {
return value;
}
public String getParameterContextId() {
return parameterContextId;
}
@Override
public boolean equals(final Object o) {
if (this == o) {

View File

@ -21,14 +21,15 @@ import java.util.Optional;
public interface ParameterLookup {
/**
* Returns the Parameter with the given name
* Returns the Parameter with the given name, considering the base and all inherited ParameterContexts.
* @param parameterName the name of the Parameter
* @return the Parameter with the given name or an empty Optional if no Parameter exists with that name
*/
Optional<Parameter> getParameter(String parameterName);
/**
* Returns false if any Parameters are available, true if no Parameters have been defined
* Returns false if any Parameters are available, true if no Parameters have been defined in this or any
* inherited ParameterContexts.
* @return true if empty
*/
boolean isEmpty();

View File

@ -17,10 +17,12 @@
package org.apache.nifi.web.api.dto;
import io.swagger.annotations.ApiModelProperty;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import javax.xml.bind.annotation.XmlType;
import java.util.List;
import java.util.Set;
@XmlType(name = "parameterContext")
@ -30,6 +32,7 @@ public class ParameterContextDTO {
private String description;
private Set<ParameterEntity> parameters;
private Set<ProcessGroupEntity> boundProcessGroups;
private List<ParameterContextReferenceEntity> inheritedParameterContexts;
public void setId(String id) {
this.identifier = id;
@ -71,6 +74,15 @@ public class ParameterContextDTO {
this.boundProcessGroups = boundProcessGroups;
}
@ApiModelProperty("A list of references of Parameter Contexts from which this one inherits parameters")
public List<ParameterContextReferenceEntity> getInheritedParameterContexts() {
return inheritedParameterContexts;
}
public void setInheritedParameterContexts(final List<ParameterContextReferenceEntity> inheritedParameterContexts) {
this.inheritedParameterContexts = inheritedParameterContexts;
}
@ApiModelProperty(value = "The Process Groups that are bound to this Parameter Context", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Set<ProcessGroupEntity> getBoundProcessGroups() {
return boundProcessGroups;

View File

@ -18,6 +18,7 @@ package org.apache.nifi.web.api.dto;
import io.swagger.annotations.ApiModelProperty;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import javax.xml.bind.annotation.XmlType;
import java.util.Set;
@ -30,6 +31,7 @@ public class ParameterDTO {
private String value;
private Boolean valueRemoved;
private Set<AffectedComponentEntity> referencingComponents;
private ParameterContextReferenceEntity parameterContext;
@ApiModelProperty("The name of the Parameter")
public String getName() {
@ -82,6 +84,15 @@ public class ParameterDTO {
return referencingComponents;
}
public void setParameterContext(final ParameterContextReferenceEntity parameterContext) {
this.parameterContext = parameterContext;
}
@ApiModelProperty("A reference to the Parameter Context that contains this one")
public ParameterContextReferenceEntity getParameterContext() {
return parameterContext;
}
public void setReferencingComponents(final Set<AffectedComponentEntity> referencingComponents) {
this.referencingComponents = referencingComponents;
}

View File

@ -39,16 +39,20 @@ import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextManager;
import org.apache.nifi.parameter.ParameterReferenceManager;
import org.apache.nifi.parameter.ReferenceOnlyParameterContext;
import org.apache.nifi.parameter.StandardParameterContext;
import org.apache.nifi.parameter.StandardParameterReferenceManager;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.remote.PublicPort;
import org.apache.nifi.remote.RemoteGroupPort;
import org.apache.nifi.util.ReflectionUtils;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -75,6 +79,8 @@ public abstract class AbstractFlowManager implements FlowManager {
private volatile ControllerServiceProvider controllerServiceProvider;
private volatile ProcessGroup rootGroup;
private final ThreadLocal<Boolean> withParameterContextResolution = ThreadLocal.withInitial(() -> false);
public AbstractFlowManager(final FlowFileEventRepository flowFileEventRepository, final ParameterContextManager parameterContextManager,
final FlowRegistryClient flowRegistryClient, final BooleanSupplier flowInitializedCheck) {
this.flowFileEventRepository = flowFileEventRepository;
@ -414,7 +420,8 @@ public abstract class AbstractFlowManager implements FlowManager {
}
@Override
public ParameterContext createParameterContext(final String id, final String name, final Map<String, Parameter> parameters) {
public ParameterContext createParameterContext(final String id, final String name, final Map<String, Parameter> parameters,
final List<ParameterContextReferenceEntity> parameterContexts) {
final boolean namingConflict = parameterContextManager.getParameterContexts().stream()
.anyMatch(paramContext -> paramContext.getName().equals(name));
@ -425,9 +432,72 @@ public abstract class AbstractFlowManager implements FlowManager {
final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(this);
final ParameterContext parameterContext = new StandardParameterContext(id, name, referenceManager, getParameterContextParent());
parameterContext.setParameters(parameters);
if (parameterContexts != null && !parameterContexts.isEmpty()) {
if (!withParameterContextResolution.get()) {
throw new IllegalStateException("A ParameterContext with inherited ParameterContexts may only be created from within a call to AbstractFlowManager#withParameterContextResolution");
}
final List<ParameterContext> parameterContextList = new ArrayList<>();
for(final ParameterContextReferenceEntity parameterContextRef : parameterContexts) {
parameterContextList.add(lookupParameterContext(parameterContextRef.getId()));
}
parameterContext.setInheritedParameterContexts(parameterContextList);
}
parameterContextManager.addParameterContext(parameterContext);
return parameterContext;
}
@Override
public void withParameterContextResolution(final Runnable parameterContextAction) {
withParameterContextResolution.set(true);
parameterContextAction.run();
withParameterContextResolution.set(false);
for (final ParameterContext parameterContext : parameterContextManager.getParameterContexts()) {
// if a param context in the manager itself is reference-only, it means there is a reference to a param
// context that no longer exists
if (parameterContext instanceof ReferenceOnlyParameterContext) {
throw new IllegalStateException(String.format("A Parameter Context tries to inherit from another Parameter Context [%s] that does not exist",
parameterContext.getIdentifier()));
}
// resolve any references nested in the actual param contexts
final List<ParameterContext> inheritedParamContexts = new ArrayList<>();
for(final ParameterContext inheritedParamContext : parameterContext.getInheritedParameterContexts()) {
if (inheritedParamContext instanceof ReferenceOnlyParameterContext) {
inheritedParamContexts.add(parameterContextManager.getParameterContext(inheritedParamContext.getIdentifier()));
} else {
inheritedParamContexts.add(inheritedParamContext);
}
}
parameterContext.setInheritedParameterContexts(inheritedParamContexts);
}
// if any reference-only inherited param contexts still exist, it means they couldn't be resolved
for (final ParameterContext parameterContext : parameterContextManager.getParameterContexts()) {
for(final ParameterContext inheritedParamContext : parameterContext.getInheritedParameterContexts()) {
if (inheritedParamContext instanceof ReferenceOnlyParameterContext) {
throw new IllegalStateException(String.format("Parameter Context [%s] tries to inherit from a Parameter Context [%s] that does not exist",
parameterContext.getName(), inheritedParamContext.getIdentifier()));
}
}
}
}
/**
* Looks up a ParameterContext by ID. If not found, registers a ReferenceOnlyParameterContext, preventing
* chicken-egg scenarios where a referenced ParameterContext is not registered before the referencing one.
* @param id A parameter context ID
* @return The matching ParameterContext, or ReferenceOnlyParameterContext if not found yet
*/
private ParameterContext lookupParameterContext(final String id) {
if (!parameterContextManager.hasParameterContext(id)) {
parameterContextManager.addParameterContext(new ReferenceOnlyParameterContext(id));
}
return parameterContextManager.getParameterContext(id);
}
protected abstract Authorizable getParameterContextParent();
}

View File

@ -140,8 +140,10 @@ import org.apache.nifi.util.ReflectionUtils;
import org.apache.nifi.util.SnippetUtils;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO;
import org.apache.nifi.web.api.dto.TemplateDTO;
import org.apache.nifi.web.api.dto.VersionedFlowDTO;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -3171,7 +3173,7 @@ public final class StandardProcessGroup implements ProcessGroup {
// For each Parameter in the updated parameter context, add a ParameterUpdate to our map
final Map<String, ParameterUpdate> updatedParameters = new HashMap<>();
for (final Map.Entry<ParameterDescriptor, Parameter> entry : updatedParameterContext.getParameters().entrySet()) {
for (final Map.Entry<ParameterDescriptor, Parameter> entry : updatedParameterContext.getEffectiveParameters().entrySet()) {
final ParameterDescriptor updatedDescriptor = entry.getKey();
final Parameter updatedParameter = entry.getValue();
@ -3186,7 +3188,7 @@ public final class StandardProcessGroup implements ProcessGroup {
}
// For each Parameter that was in the previous parameter context that is not in the updated Paramter Context, add a ParameterUpdate to our map with `null` for the updated value
for (final Map.Entry<ParameterDescriptor, Parameter> entry : previousParameterContext.getParameters().entrySet()) {
for (final Map.Entry<ParameterDescriptor, Parameter> entry : previousParameterContext.getEffectiveParameters().entrySet()) {
final ParameterDescriptor previousDescriptor = entry.getKey();
final Parameter previousParameter = entry.getValue();
@ -3206,7 +3208,7 @@ public final class StandardProcessGroup implements ProcessGroup {
private Map<String, ParameterUpdate> createParameterUpdates(final ParameterContext parameterContext, final BiFunction<ParameterDescriptor, String, ParameterUpdate> parameterUpdateMapper) {
final Map<String, ParameterUpdate> updatedParameters = new HashMap<>();
for (final Map.Entry<ParameterDescriptor, Parameter> entry : parameterContext.getParameters().entrySet()) {
for (final Map.Entry<ParameterDescriptor, Parameter> entry : parameterContext.getEffectiveParameters().entrySet()) {
final ParameterDescriptor parameterDescriptor = entry.getKey();
final Parameter parameter = entry.getValue();
@ -3979,7 +3981,7 @@ public final class StandardProcessGroup implements ProcessGroup {
group.setPosition(new Position(proposed.getPosition().getX(), proposed.getPosition().getY()));
}
updateParameterContext(group, proposed, versionedParameterContexts, componentIdSeed);
flowManager.withParameterContextResolution(() -> updateParameterContext(group, proposed, versionedParameterContexts, componentIdSeed));
updateVariableRegistry(group, proposed, variablesToSkip);
final FlowFileConcurrency flowFileConcurrency = proposed.getFlowFileConcurrency() == null ? FlowFileConcurrency.UNBOUNDED :
@ -4411,7 +4413,8 @@ public final class StandardProcessGroup implements ProcessGroup {
}
}
private ParameterContext createParameterContext(final VersionedParameterContext versionedParameterContext, final String parameterContextId) {
private ParameterContext createParameterContext(final VersionedParameterContext versionedParameterContext, final String parameterContextId,
final Map<String, VersionedParameterContext> versionedParameterContexts, final String componentIdSeed) {
final Map<String, Parameter> parameters = new HashMap<>();
for (final VersionedParameter versionedParameter : versionedParameterContext.getParameters()) {
final ParameterDescriptor descriptor = new ParameterDescriptor.Builder()
@ -4423,11 +4426,32 @@ public final class StandardProcessGroup implements ProcessGroup {
final Parameter parameter = new Parameter(descriptor, versionedParameter.getValue());
parameters.put(versionedParameter.getName(), parameter);
}
final List<ParameterContextReferenceEntity> parameterContextRefs = new ArrayList<>();
if (versionedParameterContext.getInheritedParameterContexts() != null) {
parameterContextRefs.addAll(versionedParameterContext.getInheritedParameterContexts().stream()
.map(name -> createParameterReferenceEntity(name, versionedParameterContexts, componentIdSeed))
.collect(Collectors.toList()));
}
return flowManager.createParameterContext(parameterContextId, versionedParameterContext.getName(), parameters);
return flowManager.createParameterContext(parameterContextId, versionedParameterContext.getName(), parameters, parameterContextRefs);
}
private void addMissingParameters(final VersionedParameterContext versionedParameterContext, final ParameterContext currentParameterContext) {
private ParameterContextReferenceEntity createParameterReferenceEntity(final String parameterContextName,
final Map<String, VersionedParameterContext> versionedParameterContexts,
final String componentIdSeed) {
final ParameterContextReferenceEntity entity = new ParameterContextReferenceEntity();
final ParameterContextReferenceDTO dto = new ParameterContextReferenceDTO();
final VersionedParameterContext versionedParameterContext = versionedParameterContexts.get(parameterContextName);
final ParameterContext selectedParameterContext = selectParameterContext(versionedParameterContext, componentIdSeed, versionedParameterContexts);
dto.setName(selectedParameterContext.getName());
dto.setId(selectedParameterContext.getIdentifier());
entity.setId(dto.getId());
entity.setComponent(dto);
return entity;
}
private void addMissingConfiguration(final VersionedParameterContext versionedParameterContext, final ParameterContext currentParameterContext,
final String componentIdSeed, final Map<String, VersionedParameterContext> versionedParameterContexts) {
final Map<String, Parameter> parameters = new HashMap<>();
for (final VersionedParameter versionedParameter : versionedParameterContext.getParameters()) {
final Optional<Parameter> parameterOption = currentParameterContext.getParameter(versionedParameter.getName());
@ -4447,6 +4471,15 @@ public final class StandardProcessGroup implements ProcessGroup {
}
currentParameterContext.setParameters(parameters);
// If the current parameter context doesn't have any inherited param contexts but the versioned one does,
// add the versioned ones.
if (versionedParameterContext.getInheritedParameterContexts() != null && !versionedParameterContext.getInheritedParameterContexts().isEmpty()
&& currentParameterContext.getInheritedParameterContexts().isEmpty()) {
currentParameterContext.setInheritedParameterContexts(versionedParameterContext.getInheritedParameterContexts().stream()
.map(name -> selectParameterContext(versionedParameterContexts.get(name), componentIdSeed, versionedParameterContexts))
.collect(Collectors.toList()));
}
}
private ParameterContext getParameterContextByName(final String contextName) {
@ -4473,25 +4506,30 @@ public final class StandardProcessGroup implements ProcessGroup {
+ "' does not exist in set of available parameter contexts [" + paramContextNames + "]");
}
final ParameterContext contextByName = getParameterContextByName(versionedParameterContext.getName());
final ParameterContext selectedParameterContext;
if (contextByName == null) {
final String parameterContextId = generateUuid(versionedParameterContext.getName(), versionedParameterContext.getName(), componentIdSeed);
selectedParameterContext = createParameterContext(versionedParameterContext, parameterContextId);
} else {
selectedParameterContext = contextByName;
addMissingParameters(versionedParameterContext, selectedParameterContext);
}
final ParameterContext selectedParameterContext = selectParameterContext(versionedParameterContext, componentIdSeed, versionedParameterContexts);
group.setParameterContext(selectedParameterContext);
} else {
// Update the current Parameter Context so that it has any Parameters included in the proposed context
final VersionedParameterContext versionedParameterContext = versionedParameterContexts.get(proposedParameterContextName);
addMissingParameters(versionedParameterContext, currentParamContext);
addMissingConfiguration(versionedParameterContext, currentParamContext, componentIdSeed, versionedParameterContexts);
}
}
}
private ParameterContext selectParameterContext(final VersionedParameterContext versionedParameterContext, final String componentIdSeed,
final Map<String, VersionedParameterContext> versionedParameterContexts) {
final ParameterContext contextByName = getParameterContextByName(versionedParameterContext.getName());
final ParameterContext selectedParameterContext;
if (contextByName == null) {
final String parameterContextId = generateUuid(versionedParameterContext.getName(), versionedParameterContext.getName(), componentIdSeed);
selectedParameterContext = createParameterContext(versionedParameterContext, parameterContextId, versionedParameterContexts, componentIdSeed);
} else {
selectedParameterContext = contextByName;
addMissingConfiguration(versionedParameterContext, selectedParameterContext, componentIdSeed, versionedParameterContexts);
}
return selectedParameterContext;
}
private void updateVariableRegistry(final ProcessGroup group, final VersionedProcessGroup proposed, final Set<String> variablesToSkip) {
// Determine which variables have been added/removed and add/remove them from this group's variable registry.
// We don't worry about if a variable value has changed, because variables are designed to be 'environment specific.'
@ -5633,6 +5671,17 @@ public final class StandardProcessGroup implements ProcessGroup {
return dataValve;
}
@Override
public boolean referencesParameterContext(final ParameterContext parameterContext) {
final ParameterContext ownParameterContext = this.getParameterContext();
if (ownParameterContext == null || parameterContext == null) {
return false;
}
return ownParameterContext.getIdentifier().equals(parameterContext.getIdentifier())
|| ownParameterContext.inheritsFrom(parameterContext.getIdentifier());
}
@Override
public void setDefaultFlowFileExpiration(final String defaultFlowFileExpiration) {
// use default if value not provided

View File

@ -0,0 +1,27 @@
/*
* 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;
/**
* Represents only an ID reference for a ParameterContext.
*/
public class ReferenceOnlyParameterContext extends StandardParameterContext {
public ReferenceOnlyParameterContext(String id) {
super(id, String.format("Reference-Only Parameter Context [%s]", id), ParameterReferenceManager.EMPTY, null);
}
}

View File

@ -16,10 +16,14 @@
*/
package org.apache.nifi.parameter;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.controller.ComponentNode;
import org.apache.nifi.controller.ProcessorNode;
@ -30,14 +34,18 @@ import org.apache.nifi.groups.ProcessGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Stack;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
public class StandardParameterContext implements ParameterContext {
private static final Logger logger = LoggerFactory.getLogger(StandardParameterContext.class);
@ -49,14 +57,15 @@ public class StandardParameterContext implements ParameterContext {
private String name;
private long version = 0L;
private final Map<ParameterDescriptor, Parameter> parameters = new LinkedHashMap<>();
private final List<ParameterContext> inheritedParameterContexts = new ArrayList<>();
private volatile String description;
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public StandardParameterContext(final String id, final String name, final ParameterReferenceManager parameterReferenceManager, final Authorizable parentAuthorizable) {
public StandardParameterContext(final String id, final String name, final ParameterReferenceManager parameterReferenceManager,
final Authorizable parentAuthorizable) {
this.id = Objects.requireNonNull(id);
this.name = Objects.requireNonNull(name);
this.parameterReferenceManager = parameterReferenceManager;
@ -102,43 +111,57 @@ public class StandardParameterContext implements ParameterContext {
return description;
}
@Override
public void setParameters(final Map<String, Parameter> updatedParameters) {
final Map<String, ParameterUpdate> parameterUpdates = new HashMap<>();
boolean changeAffectingComponents = false;
writeLock.lock();
try {
this.version++;
verifyCanSetParameters(updatedParameters);
final Map<ParameterDescriptor, Parameter> currentEffectiveParameters = getEffectiveParameters();
final Map<ParameterDescriptor, Parameter> effectiveProposedParameters = getEffectiveParameters(getProposedParameters(updatedParameters));
for (final Map.Entry<String, Parameter> entry : updatedParameters.entrySet()) {
final String parameterName = entry.getKey();
final Parameter parameter = entry.getValue();
final Map<String, Parameter> effectiveParameterUpdates = getEffectiveParameterUpdates(currentEffectiveParameters, effectiveProposedParameters);
if (parameter == null) {
changeAffectingComponents = true;
verifyCanSetParameters(effectiveParameterUpdates);
final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder().name(parameterName).build();
final Parameter oldParameter = parameters.remove(parameterDescriptor);
// Update the actual parameters
updateParameters(parameters, updatedParameters, true);
parameterUpdates.put(parameterName, new StandardParameterUpdate(parameterName, oldParameter.getValue(), null, parameterDescriptor.isSensitive()));
} else {
final Parameter updatedParameter = createFullyPopulatedParameter(parameter);
final Parameter oldParameter = parameters.put(updatedParameter.getDescriptor(), updatedParameter);
if (oldParameter == null || !Objects.equals(oldParameter.getValue(), updatedParameter.getValue())) {
changeAffectingComponents = true;
final String previousValue = oldParameter == null ? null : oldParameter.getValue();
parameterUpdates.put(parameterName, new StandardParameterUpdate(parameterName, previousValue, updatedParameter.getValue(), updatedParameter.getDescriptor().isSensitive()));
}
}
}
// Get a list of all effective updates in order to alert referencing components
parameterUpdates.putAll(updateParameters(currentEffectiveParameters, effectiveParameterUpdates, false));
} finally {
writeLock.unlock();
}
if (changeAffectingComponents) {
alertReferencingComponents(parameterUpdates);
}
private Map<ParameterDescriptor, Parameter> getProposedParameters(final Map<String, Parameter> proposedParameterUpdates) {
final Map<ParameterDescriptor, Parameter> proposedParameters = new HashMap<>(this.parameters);
for(final Map.Entry<String, Parameter> entry : proposedParameterUpdates.entrySet()) {
final String parameterName = entry.getKey();
final Parameter parameter = entry.getValue();
if (parameter == null) {
final Optional<Parameter> existingParameter = getParameter(parameterName);
if (existingParameter.isPresent()) {
proposedParameters.remove(existingParameter.get().getDescriptor());
}
} else {
// Remove is necessary first in case sensitivity changes
proposedParameters.remove(parameter.getDescriptor());
proposedParameters.put(parameter.getDescriptor(), parameter);
}
}
return proposedParameters;
}
/**
* Alerts all referencing components of any relevant updates.
* @param parameterUpdates A map from parameter name to ParameterUpdate (empty if none are applicable)
*/
private void alertReferencingComponents(final Map<String, ParameterUpdate> parameterUpdates) {
if (!parameterUpdates.isEmpty()) {
logger.debug("Parameter Context {} was updated. {} parameters changed ({}). Notifying all affected components.", this, parameterUpdates.size(), parameterUpdates);
for (final ProcessGroup processGroup : parameterReferenceManager.getProcessGroupsBound(this)) {
@ -153,6 +176,42 @@ public class StandardParameterContext implements ParameterContext {
}
}
/**
* Returns a map from parameter name to ParameterUpdate for any actual updates to parameters.
* @param currentParameters The current parameters
* @param updatedParameters An updated parameters map
* @param performUpdate If true, this will actually perform the updates on the currentParameters map. Otherwise,
* the updates are simply collected and returned.
* @return A map from parameter name to ParameterUpdate for any actual parameters
*/
private Map<String, ParameterUpdate> updateParameters(final Map<ParameterDescriptor, Parameter> currentParameters,
final Map<String, Parameter> updatedParameters, final boolean performUpdate) {
final Map<String, ParameterUpdate> parameterUpdates = new HashMap<>();
for (final Map.Entry<String, Parameter> entry : updatedParameters.entrySet()) {
final String parameterName = entry.getKey();
final Parameter parameter = entry.getValue();
if (parameter == null) {
final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder().name(parameterName).build();
final Parameter oldParameter = performUpdate ? currentParameters.remove(parameterDescriptor)
: currentParameters.get(parameterDescriptor);
parameterUpdates.put(parameterName, new StandardParameterUpdate(parameterName, oldParameter.getValue(), null, parameterDescriptor.isSensitive()));
} else {
final Parameter updatedParameter = createFullyPopulatedParameter(parameter);
final Parameter oldParameter = performUpdate ? currentParameters.put(updatedParameter.getDescriptor(), updatedParameter)
: currentParameters.get(updatedParameter.getDescriptor());
if (oldParameter == null || !Objects.equals(oldParameter.getValue(), updatedParameter.getValue())) {
final String previousValue = oldParameter == null ? null : oldParameter.getValue();
parameterUpdates.put(parameterName, new StandardParameterUpdate(parameterName, previousValue, updatedParameter.getValue(), updatedParameter.getDescriptor().isSensitive()));
}
}
}
return parameterUpdates;
}
/**
* When updating a Parameter, the provided 'updated' Parameter may or may not contain a value. This is done because once a Parameter is set,
* a user may want to change the description of the Parameter but cannot include the value of the Parameter in the request if the Parameter is sensitive (because
@ -212,7 +271,7 @@ public class StandardParameterContext implements ParameterContext {
public boolean isEmpty() {
readLock.lock();
try {
return parameters.isEmpty();
return getEffectiveParameters().isEmpty();
} finally {
readLock.unlock();
}
@ -228,12 +287,25 @@ public class StandardParameterContext implements ParameterContext {
// the name via the Expression Language. In this case, we want to strip out those
// escaping tick marks and use just the raw name for looking up the Parameter.
final ParameterDescriptor unescaped = unescape(parameterDescriptor);
return Optional.ofNullable(parameters.get(unescaped));
// Short circuit getEffectiveParameters if we know there are no inherited ParameterContexts
return Optional.ofNullable((inheritedParameterContexts.isEmpty() ? parameters : getEffectiveParameters())
.get(unescaped));
} finally {
readLock.unlock();
}
}
@Override
public boolean hasEffectiveValueIfRemoved(final ParameterDescriptor parameterDescriptor) {
final Map<ParameterDescriptor, List<Parameter>> allOverrides = getAllParametersIncludingOverrides();
final List<Parameter> parameters = allOverrides.get(parameterDescriptor);
if (parameters == null) {
return false;
}
return parameters.size() > 1;
}
private ParameterDescriptor unescape(final ParameterDescriptor descriptor) {
final String parameterName = descriptor.getName().trim();
if ((parameterName.startsWith("'") && parameterName.endsWith("'")) || (parameterName.startsWith("\"") && parameterName.endsWith("\""))) {
@ -257,13 +329,251 @@ public class StandardParameterContext implements ParameterContext {
}
}
@Override
public Map<ParameterDescriptor, Parameter> getEffectiveParameters() {
readLock.lock();
try {
return this.getEffectiveParameters(inheritedParameterContexts);
} finally {
readLock.unlock();
}
}
/**
* 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.
* @param proposedParameters A Map of proposed parameters that should be used in place of the current parameters
* @return The view of the parameters with all overriding applied
*/
private Map<ParameterDescriptor, Parameter> getEffectiveParameters(final Map<ParameterDescriptor, Parameter> proposedParameters) {
return getEffectiveParameters(this.inheritedParameterContexts, proposedParameters, new HashMap<>());
}
/**
* Constructs an effective view of the parameters, including nested parameters, assuming the given list of ParameterContexts.
* This allows an inspection of what parameters would be available if the given list were set in this ParameterContext.
* @param parameterContexts An ordered list of ParameterContexts from which to inherit
* @return The view of the parameters with all overriding applied
*/
private Map<ParameterDescriptor, Parameter> getEffectiveParameters(final List<ParameterContext> parameterContexts) {
return getEffectiveParameters(parameterContexts, this.parameters, new HashMap<>());
}
private Map<ParameterDescriptor, List<Parameter>> getAllParametersIncludingOverrides() {
final Map<ParameterDescriptor, List<Parameter>> allOverrides = new HashMap<>();
getEffectiveParameters(this.inheritedParameterContexts, this.parameters, allOverrides);
return allOverrides;
}
private Map<ParameterDescriptor, Parameter> getEffectiveParameters(final List<ParameterContext> parameterContexts,
final Map<ParameterDescriptor, Parameter> proposedParameters,
final Map<ParameterDescriptor, List<Parameter>> allOverrides) {
final Map<ParameterDescriptor, Parameter> effectiveParameters = new LinkedHashMap<>();
// 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));
}
// Finally, override all child parameters with our own
allOverrides.putAll(overrideParameters(effectiveParameters, proposedParameters, this));
return effectiveParameters;
}
private Map<ParameterDescriptor, List<Parameter>> overrideParameters(final Map<ParameterDescriptor, Parameter> existingParameters,
final Map<ParameterDescriptor, Parameter> overridingParameters,
final ParameterContext overridingContext) {
final Map<ParameterDescriptor, List<Parameter>> allOverrides = new HashMap<>();
for(final Map.Entry<ParameterDescriptor, Parameter> entry : existingParameters.entrySet()) {
final List<Parameter> parameters = new ArrayList<>();
parameters.add(entry.getValue());
allOverrides.put(entry.getKey(), parameters);
}
for(final Map.Entry<ParameterDescriptor, Parameter> entry : overridingParameters.entrySet()) {
final ParameterDescriptor overridingParameterDescriptor = entry.getKey();
Parameter overridingParameter = entry.getValue();
if (existingParameters.containsKey(overridingParameterDescriptor)) {
final Parameter existingParameter = existingParameters.get(overridingParameterDescriptor);
final ParameterDescriptor existingParameterDescriptor = existingParameter.getDescriptor();
if (existingParameterDescriptor.isSensitive() && !overridingParameterDescriptor.isSensitive()) {
throw new IllegalStateException(String.format("Cannot add ParameterContext because Sensitive Parameter [%s] would be overridden by " +
"a Non Sensitive Parameter with the same name", existingParameterDescriptor.getName()));
}
if (!existingParameterDescriptor.isSensitive() && overridingParameterDescriptor.isSensitive()) {
throw new IllegalStateException(String.format("Cannot add ParameterContext because Non Sensitive Parameter [%s] would be overridden by " +
"a Sensitive Parameter with the same name", existingParameterDescriptor.getName()));
}
}
if (overridingParameter.getParameterContextId() == null) {
overridingParameter = new Parameter(overridingParameter, overridingContext.getIdentifier());
}
allOverrides.computeIfAbsent(overridingParameterDescriptor, p -> new ArrayList<>()).add(overridingParameter);
existingParameters.put(overridingParameterDescriptor, overridingParameter);
}
return allOverrides;
}
@Override
public ParameterReferenceManager getParameterReferenceManager() {
return parameterReferenceManager;
}
/**
* Verifies that no cycles would exist in the ParameterContext reference graph, if this ParameterContext were
* to inherit from the given list of ParameterContexts.
* @param parameterContexts A list of proposed ParameterContexts
*/
private void verifyNoCycles(final List<ParameterContext> parameterContexts) {
final Stack<String> traversedIds = new Stack<>();
traversedIds.push(id);
verifyNoCycles(traversedIds, parameterContexts);
}
/**
* A helper method for performing a depth first search to verify there are no cycles in the proposed
* list of ParameterContexts.
* @param traversedIds A collection of already traversed ids in the graph
* @param parameterContexts The ParameterContexts for which to check for cycles
* @throws IllegalStateException If a cycle was detected
*/
private void verifyNoCycles(final Stack<String> traversedIds, final List<ParameterContext> parameterContexts) {
for (final ParameterContext parameterContext : parameterContexts) {
final String id = parameterContext.getIdentifier();
if (traversedIds.contains(id)) {
throw new IllegalStateException(String.format("Circular references in Parameter Contexts not allowed. [%s] was detected in a cycle.", parameterContext.getName()));
}
traversedIds.push(id);
verifyNoCycles(traversedIds, parameterContext.getInheritedParameterContexts());
traversedIds.pop();
}
}
@Override
public void setInheritedParameterContexts(final List<ParameterContext> inheritedParameterContexts) {
if (inheritedParameterContexts.equals(this.inheritedParameterContexts)) {
// No changes
return;
}
final Map<String, ParameterUpdate> parameterUpdates = new HashMap<>();
writeLock.lock();
try {
this.version++;
verifyNoCycles(inheritedParameterContexts);
final Map<ParameterDescriptor, Parameter> currentEffectiveParameters = getEffectiveParameters();
final Map<ParameterDescriptor, Parameter> effectiveProposedParameters = getEffectiveParameters(inheritedParameterContexts);
final Map<String, Parameter> 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);
parameterUpdates.putAll(updateParameters(currentEffectiveParameters, effectiveParameterUpdates, false));
} finally {
writeLock.unlock();
}
alertReferencingComponents(parameterUpdates);
}
/**
* Returns a map that can be used to indicate all effective parameters updates, including removed parameters.
* @param currentEffectiveParameters A current map of effective parameters
* @param effectiveProposedParameters A map of effective parameters that would result if a proposed update were applied
* @return a map that can be used to indicate all effective parameters updates, including removed parameters
*/
private static Map<String, Parameter> getEffectiveParameterUpdates(final Map<ParameterDescriptor, Parameter> currentEffectiveParameters,
final Map<ParameterDescriptor, Parameter> effectiveProposedParameters) {
final Map<String, Parameter> effectiveParameterUpdates = new HashMap<>();
for (final Map.Entry<ParameterDescriptor, Parameter> entry : effectiveProposedParameters.entrySet()) {
final ParameterDescriptor proposedParameterDescriptor = entry.getKey();
final Parameter proposedParameter = entry.getValue();
if (currentEffectiveParameters.containsKey(proposedParameterDescriptor)) {
final Parameter currentParameter = currentEffectiveParameters.get(proposedParameterDescriptor);
if (!currentParameter.equals(proposedParameter) || currentParameter.getDescriptor().isSensitive() != proposedParameter.getDescriptor().isSensitive()) {
// The parameter has been updated in some way
effectiveParameterUpdates.put(proposedParameterDescriptor.getName(), proposedParameter);
}
} else {
// It's a new parameter
effectiveParameterUpdates.put(proposedParameterDescriptor.getName(), proposedParameter);
}
}
for (final Map.Entry<ParameterDescriptor, Parameter> entry : currentEffectiveParameters.entrySet()) {
final ParameterDescriptor currentParameterDescriptor = entry.getKey();
if (!effectiveProposedParameters.containsKey(currentParameterDescriptor)) {
// If a current parameter is not in the proposed parameters, it was effectively removed
effectiveParameterUpdates.put(currentParameterDescriptor.getName(), null);
}
}
return effectiveParameterUpdates;
}
@Override
public List<ParameterContext> getInheritedParameterContexts() {
readLock.lock();
try {
return new ArrayList<>(inheritedParameterContexts);
} finally {
readLock.unlock();
}
}
@Override
public List<String> getInheritedParameterContextNames() {
readLock.lock();
try {
return inheritedParameterContexts.stream().map(ParameterContext::getName)
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
@Override
public boolean inheritsFrom(final String parameterContextId) {
readLock.lock();
try {
if (!inheritedParameterContexts.isEmpty()) {
for(final ParameterContext inheritedParameterContext : inheritedParameterContexts) {
if (inheritedParameterContext.getIdentifier().equals(parameterContextId)) {
return true;
}
if (inheritedParameterContext.inheritsFrom(parameterContextId)) {
return true;
}
}
}
return false;
} finally {
readLock.unlock();
}
}
@Override
public void verifyCanSetParameters(final Map<String, Parameter> updatedParameters) {
verifyCanSetParameters(parameters, updatedParameters);
}
public void verifyCanSetParameters(final Map<ParameterDescriptor, Parameter> currentParameters, final Map<String, Parameter> updatedParameters) {
// Ensure that the updated parameters will not result in changing the sensitivity flag of any parameter.
for (final Map.Entry<String, Parameter> entry : updatedParameters.entrySet()) {
final String parameterName = entry.getKey();
@ -278,14 +588,14 @@ public class StandardParameterContext implements ParameterContext {
throw new IllegalArgumentException("Parameter '" + parameterName + "' was specified with the wrong key in the Map");
}
validateSensitiveFlag(parameter);
validateSensitiveFlag(currentParameters, parameter);
validateReferencingComponents(parameterName, parameter, "update");
}
}
private void validateSensitiveFlag(final Parameter updatedParameter) {
private void validateSensitiveFlag(final Map<ParameterDescriptor, Parameter> currentParameters, final Parameter updatedParameter) {
final ParameterDescriptor updatedDescriptor = updatedParameter.getDescriptor();
final Parameter existingParameter = parameters.get(updatedDescriptor);
final Parameter existingParameter = currentParameters.get(updatedDescriptor);
if (existingParameter == null) {
return;
@ -357,6 +667,17 @@ public class StandardParameterContext implements ParameterContext {
return "StandardParameterContext[name=" + name + "]";
}
@Override
public void authorize(final Authorizer authorizer, final RequestAction action, final NiFiUser user) throws AccessDeniedException {
ParameterContext.super.authorize(authorizer, action, user);
if (RequestAction.READ == action) {
for (final ParameterContext parameterContext : inheritedParameterContexts) {
parameterContext.authorize(authorizer, action, user);
}
}
}
@Override
public Authorizable getParentAuthorizable() {
return new Authorizable() {

View File

@ -21,10 +21,17 @@ 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<String, ParameterContext> parameterContexts = new HashMap<>();
@Override
public boolean hasParameterContext(final String id) {
return parameterContexts.get(id) != null;
}
@Override
public synchronized ParameterContext getParameterContext(final String id) {
return parameterContexts.get(id);
@ -35,7 +42,9 @@ public class StandardParameterContextManager implements ParameterContextManager
Objects.requireNonNull(parameterContext);
if (parameterContexts.containsKey(parameterContext.getIdentifier())) {
throw new IllegalStateException("Cannot add Parameter Context because another Parameter Context already exists with the same ID");
if (!(parameterContexts.get(parameterContext.getIdentifier()) 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()) {
@ -57,4 +66,9 @@ public class StandardParameterContextManager implements ParameterContextManager
public synchronized Set<ParameterContext> getParameterContexts() {
return new HashSet<>(parameterContexts.values());
}
@Override
public Map<String, ParameterContext> getParameterContextNameMapping() {
return parameterContexts.values().stream().collect(Collectors.toMap(ParameterContext::getName, Function.identity()));
}
}

View File

@ -51,9 +51,7 @@ public class StandardParameterReferenceManager implements ParameterReferenceMana
@Override
public Set<ProcessGroup> getProcessGroupsBound(final ParameterContext parameterContext) {
final ProcessGroup rootGroup = flowManager.getRootGroup();
final String contextId = parameterContext.getIdentifier();
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(
group -> group.getParameterContext() != null && group.getParameterContext().getIdentifier().equals(contextId));
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(group -> group.referencesParameterContext(parameterContext));
return new HashSet<>(referencingGroups);
}
@ -63,9 +61,7 @@ public class StandardParameterReferenceManager implements ParameterReferenceMana
final Set<T> referencingComponents = new HashSet<>();
final ProcessGroup rootGroup = flowManager.getRootGroup();
final String contextId = parameterContext.getIdentifier();
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(
group -> group.getParameterContext() != null && group.getParameterContext().getIdentifier().equals(contextId));
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(group -> group.referencesParameterContext(parameterContext));
for (final ProcessGroup group : referencingGroups) {
for (final T componentNode : componentFunction.apply(group)) {

View File

@ -688,15 +688,7 @@ public class NiFiRegistryFlowMapper {
final Map<String, VersionedParameterContext> parameterContexts) {
final ParameterContext parameterContext = processGroup.getParameterContext();
if (parameterContext != null) {
// map this process group's parameter context and add to the collection
final Set<VersionedParameter> parameters = parameterContext.getParameters().values().stream()
.map(this::mapParameter)
.collect(Collectors.toSet());
final VersionedParameterContext versionedContext = new VersionedParameterContext();
versionedContext.setName(parameterContext.getName());
versionedContext.setParameters(parameters);
parameterContexts.put(versionedContext.getName(), versionedContext);
mapParameterContext(parameterContext, parameterContexts);
}
for (final ProcessGroup child : processGroup.getProcessGroups()) {
@ -707,6 +699,23 @@ public class NiFiRegistryFlowMapper {
}
}
private void mapParameterContext(final ParameterContext parameterContext, final Map<String, VersionedParameterContext> parameterContexts) {
// map this process group's parameter context and add to the collection
final Set<VersionedParameter> parameters = parameterContext.getParameters().values().stream()
.map(this::mapParameter)
.collect(Collectors.toSet());
final VersionedParameterContext versionedContext = new VersionedParameterContext();
versionedContext.setName(parameterContext.getName());
versionedContext.setParameters(parameters);
versionedContext.setInheritedParameterContexts(parameterContext.getInheritedParameterContextNames());
for(final ParameterContext inheritedParameterContext : parameterContext.getInheritedParameterContexts()) {
mapParameterContext(inheritedParameterContext, parameterContexts);
}
parameterContexts.put(versionedContext.getName(), versionedContext);
}
private VersionedParameter mapParameter(final Parameter parameter) {
if (parameter == null) {
return null;

View File

@ -22,11 +22,14 @@ import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -172,9 +175,7 @@ public class TestStandardParameterContext {
final HashMapParameterReferenceManager referenceManager = new HashMapParameterReferenceManager();
final StandardParameterContext context = new StandardParameterContext("unit-test-context", "unit-test-context", referenceManager, null);
final ProcessorNode procNode = Mockito.mock(ProcessorNode.class);
Mockito.when(procNode.isRunning()).thenReturn(false);
referenceManager.addProcessorReference("abc", procNode);
final ProcessorNode procNode = getProcessorNode("abc", referenceManager);
final ParameterDescriptor abcDescriptor = new ParameterDescriptor.Builder().name("abc").sensitive(true).build();
@ -190,17 +191,15 @@ public class TestStandardParameterContext {
assertEquals("321", context.getParameter("abc").get().getValue());
// Make processor 'running'
Mockito.when(procNode.isRunning()).thenReturn(true);
startProcessor(procNode);
parameters.clear();
parameters.put("abc", new Parameter(abcDescriptor, "123"));
try {
context.setParameters(parameters);
Assert.fail("Was able to change parameter while referencing processor was running");
} catch (final IllegalStateException expected) {
}
// Cannot update parameters while running
Assert.assertThrows(IllegalStateException.class, () -> context.setParameters(parameters));
// This passes no parameters to update, so it should be fine
context.setParameters(Collections.emptyMap());
parameters.clear();
@ -214,13 +213,204 @@ public class TestStandardParameterContext {
assertEquals("321", context.getParameter("abc").get().getValue());
}
@Test
public void testChangingNestedParameterForRunningProcessor() {
final String inheritedParamName = "def";
final String originalValue = "123";
final String changedValue = "321";
final HashMapParameterReferenceManager referenceManager = new HashMapParameterReferenceManager();
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup, referenceManager);
addParameter(a, "abc", "123");
final ParameterContext b = createParameterContext("b", parameterContextLookup, referenceManager);
addParameter(b, inheritedParamName, originalValue);
a.setInheritedParameterContexts(Arrays.asList(b));
// Structure is now:
// Param context A
// Param abc
// (Inherited) Param def (from B)
// Processor references param 'def'
final ProcessorNode procNode = getProcessorNode(inheritedParamName, referenceManager);
// Show that inherited param 'def' starts with the original value from B
Assert.assertEquals(originalValue, a.getParameter(inheritedParamName).get().getValue());
// Now demonstrate that we can't effectively add the parameter by referencing Context B while processor runs
a.setInheritedParameterContexts(Collections.emptyList()); // A now no longer includes 'def'
startProcessor(procNode);
try {
a.setInheritedParameterContexts(Arrays.asList(b));
Assert.fail("Was able to change effective parameter while referencing processor was running");
} catch (final IllegalStateException expected) {
Assert.assertTrue(expected.getMessage().contains("def"));
}
// Safely add Context B, and show we can't effectively remove 'def' while processor runs
stopProcessor(procNode);
a.setInheritedParameterContexts(Arrays.asList(b));
startProcessor(procNode);
try {
a.setInheritedParameterContexts(Collections.emptyList());
Assert.fail("Was able to remove parameter while referencing processor was running");
} catch (final IllegalStateException expected) {
Assert.assertTrue(expected.getMessage().contains("def"));
}
// Show we can't effectively change the value by changing it in B
try {
addParameter(b, inheritedParamName, changedValue);
Assert.fail("Was able to change parameter while referencing processor was running");
} catch (final IllegalStateException expected) {
Assert.assertTrue(expected.getMessage().contains("def"));
}
assertEquals(originalValue, a.getParameter(inheritedParamName).get().getValue());
// Show we can't effectively change the value by adding Context C with 'def' ahead of 'B'
stopProcessor(procNode);
final ParameterContext c = createParameterContext("c", parameterContextLookup, referenceManager);
addParameter(c, inheritedParamName, changedValue);
startProcessor(procNode);
try {
a.setInheritedParameterContexts(Arrays.asList(c, b));
Assert.fail("Was able to change parameter while referencing processor was running");
} catch (final IllegalStateException expected) {
Assert.assertTrue(expected.getMessage().contains("def"));
}
assertEquals(originalValue, a.getParameter(inheritedParamName).get().getValue());
// Show that if the effective value of 'def' doesn't change, we don't prevent updating
// ParameterContext references that refer to 'def'
a.setInheritedParameterContexts(Arrays.asList(b, c));
assertEquals(originalValue, a.getParameter(inheritedParamName).get().getValue());
stopProcessor(procNode);
removeParameter(b, inheritedParamName);
b.setInheritedParameterContexts(Collections.singletonList(c));
// Now a gets 'def' by inheriting through B and then C.
// Show that updating a value on a grandchild is prevented because the processor is running and
// references the parameter via the grandparent
startProcessor(procNode);
Assert.assertThrows(IllegalStateException.class, () -> removeParameter(c, inheritedParamName));
}
private static ProcessorNode getProcessorNode(String parameterName, HashMapParameterReferenceManager referenceManager) {
final ProcessorNode procNode = Mockito.mock(ProcessorNode.class);
Mockito.when(procNode.isRunning()).thenReturn(false);
referenceManager.addProcessorReference(parameterName, procNode);
return procNode;
}
private static void startProcessor(final ProcessorNode processorNode) {
setProcessorRunning(processorNode, true);
}
private static void stopProcessor(final ProcessorNode processorNode) {
setProcessorRunning(processorNode, false);
}
private static void setProcessorRunning(final ProcessorNode processorNode, final boolean isRunning) {
Mockito.when(processorNode.isRunning()).thenReturn(isRunning);
}
private static void setControllerServiceState(final ControllerServiceNode serviceNode, final ControllerServiceState state) {
Mockito.when(serviceNode.getState()).thenReturn(state);
}
private static void enableControllerService(final ControllerServiceNode serviceNode) {
setControllerServiceState(serviceNode, ControllerServiceState.ENABLED);
}
@Test
public void testAlertReferencingComponents() {
final String inheritedParamName = "def";
final String originalValue = "123";
final String changedValue = "321";
final HashMapParameterReferenceManager referenceManager = Mockito.spy(new HashMapParameterReferenceManager());
final Set<ProcessGroup> processGroups = new HashSet<>();
final ProcessGroup processGroup = Mockito.mock(ProcessGroup.class);
processGroups.add(processGroup);
Mockito.when(referenceManager.getProcessGroupsBound(ArgumentMatchers.any())).thenReturn(processGroups);
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup, referenceManager);
addParameter(a, "abc", "123");
final ParameterContext b = createParameterContext("b", parameterContextLookup, referenceManager);
addParameter(b, inheritedParamName, originalValue);
getProcessorNode(inheritedParamName, referenceManager);
a.setInheritedParameterContexts(Arrays.asList(b));
// Once for setting abc, once for setting def, and once for adding B to context A
Mockito.verify(processGroup, Mockito.times(3)).onParameterContextUpdated(ArgumentMatchers.anyMap());
}
@Test
public void testChangingNestedParameterForEnabledControllerService() {
final String inheritedParamName = "def";
final String inheritedParamName2 = "ghi";
final String originalValue = "123";
final String changedValue = "321";
final HashMapParameterReferenceManager referenceManager = new HashMapParameterReferenceManager();
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup, referenceManager);
addParameter(a, "abc", "123");
final ParameterContext b = createParameterContext("b", parameterContextLookup, referenceManager);
addParameter(b, inheritedParamName, originalValue);
a.setInheritedParameterContexts(Arrays.asList(b));
final ParameterContext c = createParameterContext("c", parameterContextLookup, referenceManager);
addParameter(c, "ghi", originalValue);
// Structure is now:
// Param context A
// Param abc
// (Inherited) Param def (from B)
final ControllerServiceNode serviceNode = Mockito.mock(ControllerServiceNode.class);
enableControllerService(serviceNode);
referenceManager.addControllerServiceReference(inheritedParamName, serviceNode);
referenceManager.addControllerServiceReference(inheritedParamName2, serviceNode);
for (final ControllerServiceState state : EnumSet.of(ControllerServiceState.ENABLED, ControllerServiceState.ENABLING, ControllerServiceState.DISABLING)) {
setControllerServiceState(serviceNode, state);
Assert.assertThrows(IllegalStateException.class, () -> addParameter(b, inheritedParamName, changedValue));
Assert.assertThrows(IllegalStateException.class, () -> b.setInheritedParameterContexts(Collections.singletonList(c)));
assertEquals(originalValue, a.getParameter(inheritedParamName).get().getValue());
}
Assert.assertThrows(IllegalStateException.class, () -> removeParameter(b, inheritedParamName));
setControllerServiceState(serviceNode, ControllerServiceState.DISABLED);
b.setInheritedParameterContexts(Collections.singletonList(c));
setControllerServiceState(serviceNode, ControllerServiceState.DISABLING);
Assert.assertThrows(IllegalStateException.class, () -> b.setInheritedParameterContexts(Collections.emptyList()));
}
@Test
public void testChangingParameterForEnabledControllerService() {
final HashMapParameterReferenceManager referenceManager = new HashMapParameterReferenceManager();
final StandardParameterContext context = new StandardParameterContext("unit-test-context", "unit-test-context", referenceManager, null);
final ControllerServiceNode serviceNode = Mockito.mock(ControllerServiceNode.class);
Mockito.when(serviceNode.getState()).thenReturn(ControllerServiceState.ENABLED);
enableControllerService(serviceNode);
final ParameterDescriptor abcDescriptor = new ParameterDescriptor.Builder().name("abc").sensitive(true).build();
final Map<String, Parameter> parameters = new HashMap<>();
@ -234,7 +424,7 @@ public class TestStandardParameterContext {
parameters.put("abc", new Parameter(abcDescriptor, "321"));
for (final ControllerServiceState state : EnumSet.of(ControllerServiceState.ENABLED, ControllerServiceState.ENABLING, ControllerServiceState.DISABLING)) {
Mockito.when(serviceNode.getState()).thenReturn(state);
setControllerServiceState(serviceNode, state);
try {
context.setParameters(parameters);
@ -256,7 +446,307 @@ public class TestStandardParameterContext {
}
}
@Test
public void testSetParameterContexts_foundCycle() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
// Set up a hierarchy as follows:
// a
// / |
// b c
// / |
// d e
// |
// a (cyclical)
//
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
final ParameterContext c = createParameterContext("c", parameterContextLookup);
final ParameterContext d = createParameterContext("d", parameterContextLookup, a); // Here's the cycle
final ParameterContext e = createParameterContext("e", parameterContextLookup);
b.setInheritedParameterContexts(Arrays.asList(d, e));
Assert.assertThrows(IllegalStateException.class, () -> a.setInheritedParameterContexts(Arrays.asList(b, c)));
}
@Test
public void testSetParameterContexts_duplicationButNoCycle() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
// Set up a hierarchy as follows:
// a
// / |
// b c
// / |
// d e
// |
// c (duplicate node, but not a cycle)
//
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
final ParameterContext c = createParameterContext("c", parameterContextLookup);
final ParameterContext d = createParameterContext("d", parameterContextLookup, c); // Here's the duplicate
final ParameterContext e = createParameterContext("e", parameterContextLookup);
b.setInheritedParameterContexts(Arrays.asList(d, e));
a.setInheritedParameterContexts(Arrays.asList(b, c));
}
@Test
public void testSetParameterContexts_success() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
final ParameterContext c = createParameterContext("c", parameterContextLookup);
final ParameterContext d = createParameterContext("d", parameterContextLookup);
final ParameterContext e = createParameterContext("e", parameterContextLookup);
final ParameterContext f = createParameterContext("f", parameterContextLookup);
b.setInheritedParameterContexts(Arrays.asList(d, e));
d.setInheritedParameterContexts(Arrays.asList(f));
a.setInheritedParameterContexts(Arrays.asList(b, c));
Assert.assertEquals(Arrays.asList(b, c), a.getInheritedParameterContexts());
Assert.assertArrayEquals(new String[] {"B", "C"}, a.getInheritedParameterContextNames().toArray());
}
@Test
public void testGetEffectiveParameters() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
// Set up a hierarchy as follows:
// a
// / |
// b c
// |
// d
//
// Parameter priority should be: a, b, c, d
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterDescriptor foo = addParameter(a, "foo", "a.foo"); // Should take precedence over all other foo params
final ParameterDescriptor bar = addParameter(a, "bar", "a.bar"); // Should take precedence over all other foo params
final ParameterContext b = createParameterContext("b", parameterContextLookup);
addParameter(b,"foo", "b.foo"); // Overridden by a.foo since a is the parent
final ParameterDescriptor child = addParameter(b, "child", "b.child");
final ParameterContext c = createParameterContext("c", parameterContextLookup);
addParameter(c, "foo", "c.foo"); // Overridden by a.foo since a is the parent
addParameter(c, "child", "c.child"); // Overridden by b.child since b comes first in the list
final ParameterDescriptor secondChild = addParameter(c, "secondChild", "c.secondChild");
final ParameterContext d = createParameterContext("d", parameterContextLookup);
addParameter(d, "foo", "d.foo"); // Overridden by a.foo since a is the grandparent
addParameter(d, "child", "d.child"); // Overridden by b.foo since b is the parent
final ParameterDescriptor grandchild = addParameter(d, "grandchild", "d.grandchild");
a.setInheritedParameterContexts(Arrays.asList(b, c));
b.setInheritedParameterContexts(Arrays.asList(d));
final Map<ParameterDescriptor, Parameter> effectiveParameters = a.getEffectiveParameters();
Assert.assertEquals(5, effectiveParameters.size());
Assert.assertEquals("a.foo", effectiveParameters.get(foo).getValue());
Assert.assertEquals("a", effectiveParameters.get(foo).getParameterContextId());
Assert.assertEquals("a.bar", effectiveParameters.get(bar).getValue());
Assert.assertEquals("a", effectiveParameters.get(bar).getParameterContextId());
Assert.assertEquals("b.child", effectiveParameters.get(child).getValue());
Assert.assertEquals("b", effectiveParameters.get(child).getParameterContextId());
Assert.assertEquals("c.secondChild", effectiveParameters.get(secondChild).getValue());
Assert.assertEquals("c", effectiveParameters.get(secondChild).getParameterContextId());
Assert.assertEquals("d.grandchild", effectiveParameters.get(grandchild).getValue());
Assert.assertEquals("d", effectiveParameters.get(grandchild).getParameterContextId());
}
@Test
public void testHasEffectiveValueIfRemoved() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
// Set up a hierarchy as follows:
// a (foo, bar, baz)
// |
// b (foo, child)
// |
// c (bar, grandchild)
//
// foo is in a/b; bar is in a/c; baz is only in a
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterDescriptor foo = addParameter(a, "foo", "a.foo");
final ParameterDescriptor bar = addParameter(a, "bar", "a.bar");
final ParameterDescriptor baz = addParameter(a, "baz", "a.baz");
final ParameterContext b = createParameterContext("b", parameterContextLookup);
addParameter(b,"foo", "b.foo");
final ParameterDescriptor child = addParameter(b, "child", "b.child");
final ParameterContext c = createParameterContext("c", parameterContextLookup);
addParameter(c, "bar", "c.foo");
addParameter(c, "grandchild", "c.child");
a.setInheritedParameterContexts(Arrays.asList(b));
b.setInheritedParameterContexts(Arrays.asList(c));
assertTrue(a.hasEffectiveValueIfRemoved(foo));
assertTrue(a.hasEffectiveValueIfRemoved(bar));
assertFalse(a.hasEffectiveValueIfRemoved(baz));
}
@Test
public void testGetEffectiveParameters_duplicateOverride() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
// Set up a hierarchy as follows:
// a
// / |
// c b
// |
// d
// |
// c
//
// Parameter priority should be: a, c, b, d
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
final ParameterDescriptor child = addParameter(b, "child", "b.child"); // Overridden by c.child since c comes first in the list
final ParameterContext c = createParameterContext("c", parameterContextLookup);
addParameter(c, "child", "c.child");
final ParameterContext d = createParameterContext("d", parameterContextLookup);
addParameter(d, "child", "d.child"); // Overridden by c.foo since c precedes d's ancestor b
a.setInheritedParameterContexts(Arrays.asList(c, b));
b.setInheritedParameterContexts(Arrays.asList(d));
d.setInheritedParameterContexts(Arrays.asList(c));
final Map<ParameterDescriptor, Parameter> effectiveParameters = a.getEffectiveParameters();
Assert.assertEquals(1, effectiveParameters.size());
Assert.assertEquals("c.child", effectiveParameters.get(child).getValue());
Assert.assertEquals("c", effectiveParameters.get(child).getParameterContextId());
}
@Test
public void testSetParameterContexts_noParameterConflict() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup);
addParameter(a, "foo", "a.foo", true);
addParameter(a, "bar", "a.bar", false);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
addParameter(b,"foo", "b.foo", true); // Sensitivity matches, no conflict
addParameter(b, "child", "b.child", false);
a.setInheritedParameterContexts(Arrays.asList(b));
Assert.assertEquals(Arrays.asList(b), a.getInheritedParameterContexts());
}
@Test
public void testInheritsFrom() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
final ParameterContext c = createParameterContext("c", parameterContextLookup);
final ParameterContext d = createParameterContext("d", parameterContextLookup);
final ParameterContext e = createParameterContext("e", parameterContextLookup);
a.setInheritedParameterContexts(Arrays.asList(b));
b.setInheritedParameterContexts(Arrays.asList(c, d));
d.setInheritedParameterContexts(Arrays.asList(e));
Assert.assertTrue(a.inheritsFrom("b"));
Assert.assertTrue(a.inheritsFrom("c"));
Assert.assertTrue(a.inheritsFrom("d"));
Assert.assertTrue(a.inheritsFrom("e"));
Assert.assertFalse(a.inheritsFrom("a"));
Assert.assertTrue(b.inheritsFrom("e"));
Assert.assertFalse(b.inheritsFrom("a"));
}
@Test
public void testSetParameterContexts_parameterSensitivityConflict() {
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
final ParameterContext a = createParameterContext("a", parameterContextLookup);
addParameter(a, "foo", "a.foo", true);
addParameter(a, "bar", "a.bar", false);
final ParameterContext b = createParameterContext("b", parameterContextLookup);
addParameter(b,"foo", "b.foo", false); // Sensitivity does not match!
addParameter(b, "child", "b.child", false);
try {
a.setInheritedParameterContexts(Arrays.asList(b));
Assert.fail("Should get a failure for sensitivity mismatch in overriding");
} catch (IllegalStateException e) {
Assert.assertTrue(e.getMessage().contains("foo"));
}
Assert.assertEquals(Collections.emptyList(), a.getInheritedParameterContexts());
// Now switch and set a.foo to non-sensitive and b.foo to sensitive
removeParameter(a, "foo");
addParameter(a, "foo", "a.foo", false);
removeParameter(b, "foo");
addParameter(b, "foo", "a.foo", true);
try {
a.setInheritedParameterContexts(Arrays.asList(b));
Assert.fail("Should get a failure for sensitivity mismatch in overriding");
} catch (IllegalStateException e) {
Assert.assertTrue(e.getMessage().contains("foo"));
}
Assert.assertEquals(Collections.emptyList(), a.getInheritedParameterContexts());
}
private static void removeParameter(final ParameterContext parameterContext, final String name) {
final Map<String, Parameter> parameters = new HashMap<>();
for(final Map.Entry<ParameterDescriptor, Parameter> entry : parameterContext.getParameters().entrySet()) {
if (entry.getKey().getName().equals(name)) {
parameters.put(name, null);
} else {
parameters.put(entry.getKey().getName(), entry.getValue());
}
}
parameterContext.setParameters(parameters);
}
private static ParameterDescriptor addParameter(final ParameterContext parameterContext, final String name, final String value) {
return addParameter(parameterContext, name, value, false);
}
private static ParameterDescriptor addParameter(final ParameterContext parameterContext, final String name, final String value, final boolean isSensitive) {
final Map<String, Parameter> parameters = new HashMap<>();
for(final Map.Entry<ParameterDescriptor, Parameter> entry : parameterContext.getParameters().entrySet()) {
parameters.put(entry.getKey().getName(), entry.getValue());
}
final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder().name(name).sensitive(isSensitive).build();
parameters.put(name, new Parameter(parameterDescriptor, value));
parameterContext.setParameters(parameters);
return parameterDescriptor;
}
private static ParameterContext createParameterContext(final String id, final ParameterContextManager parameterContextLookup,
final ParameterContext... children) {
return createParameterContext(id, parameterContextLookup, ParameterReferenceManager.EMPTY, children);
}
private static ParameterContext createParameterContext(final String id, final ParameterContextManager parameterContextLookup,
final ParameterReferenceManager referenceManager, final ParameterContext... children) {
final ParameterContext parameterContext = new StandardParameterContext(id, id.toUpperCase(), referenceManager, null );
parameterContext.setInheritedParameterContexts(Arrays.asList(children));
parameterContextLookup.addParameterContext(parameterContext);
return parameterContext;
}
private static class HashMapParameterReferenceManager implements ParameterReferenceManager {
private final Map<String, ProcessorNode> processors = new HashMap<>();

View File

@ -33,9 +33,11 @@ import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextManager;
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@ -323,7 +325,41 @@ public interface FlowManager {
void removeRootControllerService(final ControllerServiceNode service);
ParameterContext createParameterContext(String id, String name, Map<String, Parameter> parameters);
/**
* Creates a <code>ParameterContext</code>. Note that in order to safely create a <code>ParameterContext</code> that includes
* inherited <code>ParameterContext</code>s, the action must be performed using {@link FlowManager#withParameterContextResolution(Runnable)},
* which ensures that all inherited <code>ParameterContext</code>s are resolved. If <code>parameterContexts</code> is
* not empty and this method is called outside of {@link FlowManager#withParameterContextResolution(Runnable)},
* <code>IllegalStateException</code> is thrown. See {@link FlowManager#withParameterContextResolution(Runnable)}
* for example usage.
*
* @param id The unique id
* @param name The ParameterContext name
* @param parameters The Parameters
* @param parameterContexts Optional inherited ParameterContexts
* @return The created ParameterContext
* @throws IllegalStateException If <code>parameterContexts</code> is not empty and this method is called without being wrapped
* by {@link FlowManager#withParameterContextResolution(Runnable)}
*/
ParameterContext createParameterContext(String id, String name, Map<String, Parameter> parameters, List<ParameterContextReferenceEntity> parameterContexts);
/**
* Performs the given ParameterContext-related action, and then resolves all inherited ParameterContext references.
* Example usage: <br/><br/>
* <pre>
* // This ensures that regardless of the order of parameter contexts created in the loop,
* // all inherited parameter contexts will be resolved if possible. If not possible, IllegalStateException is thrown.
* flowManager.withParameterContextResolution(() -> {
* for (final ParameterContextDTO dto : parameterContextDtos) {
* flowManager.createParameterContext(dto.getId(), dto.getName(), parameters, dto.getInheritedParameterContexts());
* }
* });
* </pre>
* @param parameterContextAction A runnable action, usually involving creating a ParameterContext, that requires
* parameter context references to be resolved after it is performed
* @throws IllegalStateException if an invalid parameter context reference was detected
*/
void withParameterContextResolution(Runnable parameterContextAction);
ParameterContextManager getParameterContextManager();

View File

@ -1175,6 +1175,13 @@ public interface ProcessGroup extends ComponentAuthorizable, Positionable, Versi
*/
DataValve getDataValve();
/**
* @param parameterContext A ParameterContext
* @return True if the provided ParameterContext is referenced by this Process Group, either directly or
* indirectly through inherited ParameterContexts.
*/
boolean referencesParameterContext(ParameterContext parameterContext);
/**
* @return the default flowfile expiration of the ProcessGroup
*/

View File

@ -18,6 +18,7 @@ package org.apache.nifi.parameter;
import org.apache.nifi.authorization.resource.ComponentAuthorizable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -68,9 +69,9 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable
*/
void verifyCanSetParameters(Map<String, Parameter> parameters);
/**
* Returns the Parameter with the given descriptor
* Returns the Parameter with the given descriptor, considering this and all inherited
* ParameterContexts.
*
* @param parameterDescriptor descriptor for the parameter
* @return the Parameter with the given name, or <code>null</code> if no parameter exists with the given descriptor
@ -78,18 +79,77 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable
Optional<Parameter> getParameter(ParameterDescriptor parameterDescriptor);
/**
* Returns the Map of all Parameters in this context. Note that the Map that is returned may either be immutable or may be a defensive copy but
* modifying the Map that is returned will have no effect on the contents of this Parameter Context.
* Checks whether this ParameterContext would still have an effective value for the given parameter if the
* parameter was removed from this or any inherited parameter context, no matter how indirect. This allows
* the ParameterContext to be checked for validity: if it will still have an effective value, the parameter
* can be safely removed.
*
* @param parameterDescriptor parameter descriptor to check
* @return True if, when the parameter is removed, this ParameterContext would still have an effective value
* for the parameter.
*/
boolean hasEffectiveValueIfRemoved(ParameterDescriptor parameterDescriptor);
/**
* Returns the Map of all Parameters in this context (not in any inherited ParameterContexts). Note that the Map that
* is returned may either be immutable or may be a defensive copy but modifying the Map that is returned will have
* no effect on the contents of this Parameter Context.
*
* @return a Map that contains all Parameters in the context keyed by their descriptors
*/
Map<ParameterDescriptor, Parameter> getParameters();
/**
* Returns the Map of all Parameters in this context, as well as in all inherited ParameterContexts. Any duplicate
* parameters will be overridden as described in {@link #setInheritedParameterContexts(List) setParameterContexts}.
* Note that the Map that is returned may either be immutable or may be a defensive copy but
* modifying the Map that is returned will have no effect on the contents of this Parameter Context or any other.
*
* @return a Map that contains all Parameters in the context and all nested ParameterContexts, keyed by their descriptors
*/
Map<ParameterDescriptor, Parameter> getEffectiveParameters();
/**
* Returns the ParameterReferenceManager that is associated with this ParameterContext
* @return the ParameterReferenceManager that is associated with this ParameterContext
*/
ParameterReferenceManager getParameterReferenceManager();
/**
* 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.
* The order of the list specifies the priority of parameter overriding, where parameters in the first ParameterContext in the list have
* top priority. However, all parameters in this ParameterContext take precedence over any in its list of inherited ParameterContexts.
* Note that this method should only update the ordering of the ParameterContexts, it cannot be used to modify the
* contents of the ParameterContexts in the list.
*
* @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)
*/
void setInheritedParameterContexts(List<ParameterContext> inheritedParameterContexts);
/**
* Returns a list of ParameterContexts from which this ParameterContext inherits parameters.
* See {@link #setInheritedParameterContexts(List) setParameterContexts} for further information. Note that the List that is returned may
* either be immutable or may be a defensive copy but modifying the list will not update the ParameterContexts inherited by this one.
* @return An ordered list of ParameterContexts from which this one inherits parameters
*/
List<ParameterContext> getInheritedParameterContexts();
/**
* Returns a list of names of ParameterContexts from which this ParameterContext inherits parameters.
* See {@link #setInheritedParameterContexts(List) setParameterContexts} for further information. Note that the List that is returned may
* either be immutable or may be a defensive copy but modifying the list will not update the ParameterContexts inherited by this one.
* @return An ordered list of ParameterContext names from which this one inherits parameters
*/
List<String> getInheritedParameterContextNames();
/**
* Returns true if this ParameterContext inherits from the given parameter context, either
* directly or indirectly.
* @param parameterContextId The ID of the sought parameter context
* @return True if this inherits from the given ParameterContext
*/
boolean inheritsFrom(String parameterContextId);
}

View File

@ -0,0 +1,48 @@
/*
* 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;
public interface ParameterContextLookup {
/**
* Returns true if the lookup has the given parameter context.
*
* @param id the id of the parameter context
* @return true if the context has been found
*/
boolean hasParameterContext(String id);
/**
* Gets the specified parameter context.
*
* @param id the id of the parameter context
* @return the parameter context
*/
ParameterContext getParameterContext(String id);
ParameterContextLookup EMPTY = new ParameterContextLookup() {
@Override
public boolean hasParameterContext(String id) {
return false;
}
@Override
public ParameterContext getParameterContext(final String id) {
return null;
}
};
}

View File

@ -16,14 +16,16 @@
*/
package org.apache.nifi.parameter;
import java.util.Map;
import java.util.Set;
public interface ParameterContextManager {
ParameterContext getParameterContext(String id);
public interface ParameterContextManager extends ParameterContextLookup {
void addParameterContext(ParameterContext parameterContext);
ParameterContext removeParameterContext(String parameterContextId);
Set<ParameterContext> getParameterContexts();
Map<String, ParameterContext> getParameterContextNameMapping();
}

View File

@ -418,15 +418,16 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
client.addFlowRegistry(registryId, registryName, registryUrl, description);
}
}
final Element parameterContextsElement = DomUtils.getChild(rootElement, "parameterContexts");
if (parameterContextsElement != null) {
final List<Element> contextElements = DomUtils.getChildElementsByTagName(parameterContextsElement, "parameterContext");
for (final Element contextElement : contextElements) {
final ParameterContextDTO parameterContextDto = FlowFromDOMFactory.getParameterContext(contextElement, encryptor);
createParameterContext(parameterContextDto, controller.getFlowManager());
controller.getFlowManager().withParameterContextResolution(() -> {
final Element parameterContextsElement = DomUtils.getChild(rootElement, "parameterContexts");
if (parameterContextsElement != null) {
final List<Element> contextElements = DomUtils.getChildElementsByTagName(parameterContextsElement, "parameterContext");
for (final Element contextElement : contextElements) {
final ParameterContextDTO parameterContextDto = FlowFromDOMFactory.getParameterContext(contextElement, encryptor);
createParameterContext(parameterContextDto, controller.getFlowManager());
}
}
}
});
logger.trace("Adding root process group");
rootGroup = addProcessGroup(controller, /* parent group */ null, rootGroupElement, encryptor, encodingVersion);
@ -529,7 +530,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
.map(this::createParameter)
.collect(Collectors.toMap(param -> param.getDescriptor().getName(), Function.identity()));
final ParameterContext context = flowManager.createParameterContext(dto.getId(), dto.getName(), parameters);
final ParameterContext context = flowManager.createParameterContext(dto.getId(), dto.getName(), parameters, dto.getInheritedParameterContexts());
context.setDescription(dto.getDescription());
return context;
}

View File

@ -168,6 +168,14 @@ public class FlowFromDOMFactory {
parameterEntity.setParameter(parameterDto);
parameterDtos.add(parameterEntity);
}
final List<Element> inheritedParameterContextIds = FlowFromDOMFactory.getChildrenByTagName(element, "inheritedParameterContextId");
final List<ParameterContextReferenceEntity> parameterContexts = new ArrayList<>();
for (final Element inheritedParameterContextElement : inheritedParameterContextIds) {
final ParameterContextReferenceEntity parameterContextReference = new ParameterContextReferenceEntity();
parameterContextReference.setId(inheritedParameterContextElement.getTextContent());
parameterContexts.add(parameterContextReference);
}
dto.setInheritedParameterContexts(parameterContexts);
dto.setParameters(parameterDtos);

View File

@ -161,6 +161,10 @@ public class StandardFlowSerializer implements FlowSerializer<Document> {
addStringElement(parameterContextElement, "name", parameterContext.getName());
addStringElement(parameterContextElement, "description", parameterContext.getDescription());
for(final ParameterContext childContext : parameterContext.getInheritedParameterContexts()) {
addStringElement(parameterContextElement, "inheritedParameterContextId", childContext.getIdentifier());
}
for (final Parameter parameter : parameterContext.getParameters().values()) {
addParameter(parameterContextElement, parameter);
}

View File

@ -312,6 +312,15 @@ public class FingerprintFactory {
}
}
final List<Element> inheritedParameterContexts = DomUtils.getChildElementsByTagName(parameterContextElement, "inheritedParameterContextId");
if (inheritedParameterContexts == null || inheritedParameterContexts.isEmpty()) {
builder.append("NO_INHERITED_PARAMETER_CONTEXT_IDS");
} else {
for (final Element inheritedParameterContextId : inheritedParameterContexts) {
builder.append(inheritedParameterContextId.getTextContent());
}
}
return builder;
}

View File

@ -68,6 +68,7 @@
<xs:element name="id" type="NonEmptyStringType" />
<xs:element name="name" type="NonEmptyStringType" />
<xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="inheritedParameterContextId" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="parameter" type="ParameterType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>

View File

@ -36,6 +36,7 @@ import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.controller.reporting.ReportingTaskInstantiationException;
import org.apache.nifi.controller.repository.FlowFileEventRepository;
import org.apache.nifi.controller.scheduling.StandardProcessScheduler;
import org.apache.nifi.controller.serialization.FlowSynchronizationException;
import org.apache.nifi.controller.serialization.FlowSynchronizer;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
@ -53,6 +54,9 @@ import org.apache.nifi.nar.ExtensionDiscoveringManager;
import org.apache.nifi.nar.InstanceClassLoader;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.provenance.MockProvenanceRepository;
import org.apache.nifi.registry.VariableRegistry;
@ -66,9 +70,11 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO;
import org.apache.nifi.web.api.dto.PositionDTO;
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -101,6 +107,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -327,6 +334,91 @@ public class TestFlowController {
}
}
@Test(expected = FlowSynchronizationException.class)
public void testSynchronizeFlowWithInvalidParameterContextReference() throws IOException {
final FlowSynchronizer standardFlowSynchronizer = new StandardFlowSynchronizer(
PropertyEncryptorFactory.getPropertyEncryptor(nifiProperties), nifiProperties, extensionManager);
final File flowFile = new File("src/test/resources/conf/parameter-context-flow-error.xml");
final String flow = IOUtils.toString(new FileInputStream(flowFile), StandardCharsets.UTF_8);
final String authFingerprint = "<authorizations></authorizations>";
final DataFlow proposedDataFlow = new StandardDataFlow(flow.getBytes(StandardCharsets.UTF_8), null, authFingerprint.getBytes(StandardCharsets.UTF_8), Collections.emptySet());
try {
controller.synchronize(standardFlowSynchronizer, proposedDataFlow, Mockito.mock(FlowService.class));
controller.initializeFlow();
} finally {
purgeFlow();
}
}
@Test
public void testSynchronizeFlowWithNestedParameterContexts() throws IOException {
final FlowSynchronizer standardFlowSynchronizer = new StandardFlowSynchronizer(
PropertyEncryptorFactory.getPropertyEncryptor(nifiProperties), nifiProperties, extensionManager);
final File flowFile = new File("src/test/resources/conf/parameter-context-flow.xml");
final String flow = IOUtils.toString(new FileInputStream(flowFile), StandardCharsets.UTF_8);
final String authFingerprint = "<authorizations></authorizations>";
final DataFlow proposedDataFlow = new StandardDataFlow(flow.getBytes(StandardCharsets.UTF_8), null, authFingerprint.getBytes(StandardCharsets.UTF_8), Collections.emptySet());
try {
controller.synchronize(standardFlowSynchronizer, proposedDataFlow, Mockito.mock(FlowService.class));
controller.initializeFlow();
ParameterContext parameterContext = controller.getFlowManager().getParameterContextManager().getParameterContext("context");
Assert.assertNotNull(parameterContext);
Assert.assertEquals(2, parameterContext.getInheritedParameterContexts().size());
Assert.assertEquals("referenced-context", parameterContext.getInheritedParameterContexts().get(0).getIdentifier());
Assert.assertEquals("referenced-context-2", parameterContext.getInheritedParameterContexts().get(1).getIdentifier());
} finally {
purgeFlow();
}
}
@Test
public void testCreateParameterContextWithAndWithoutValidation() throws IOException {
final FlowSynchronizer standardFlowSynchronizer = new StandardFlowSynchronizer(
PropertyEncryptorFactory.getPropertyEncryptor(nifiProperties), nifiProperties, extensionManager);
final File flowFile = new File("src/test/resources/conf/parameter-context-flow.xml");
final String flow = IOUtils.toString(new FileInputStream(flowFile), StandardCharsets.UTF_8);
final String authFingerprint = "<authorizations></authorizations>";
final DataFlow proposedDataFlow = new StandardDataFlow(flow.getBytes(StandardCharsets.UTF_8), null, authFingerprint.getBytes(StandardCharsets.UTF_8), Collections.emptySet());
try {
controller.synchronize(standardFlowSynchronizer, proposedDataFlow, Mockito.mock(FlowService.class));
controller.initializeFlow();
final Map<String, Parameter> parameters = new HashMap<>();
parameters.put("param", new Parameter(new ParameterDescriptor.Builder().name("param").build(), "value"));
// No problem since there are no inherited parameter contexts
controller.getFlowManager().createParameterContext("id", "name", parameters, Collections.emptyList());
final ParameterContext existingParameterContext = controller.getFlowManager().getParameterContextManager().getParameterContext("context");
final ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity();
ref.setId(existingParameterContext.getIdentifier());
final ParameterContextReferenceDTO dto = new ParameterContextReferenceDTO();
dto.setId(existingParameterContext.getIdentifier());
dto.setName(existingParameterContext.getName());
// This is not wrapped in FlowManager#withParameterContextResolution(Runnable), so it will throw an exception
assertThrows(IllegalStateException.class, () ->
controller.getFlowManager().createParameterContext("id", "name", parameters, Collections.singletonList(ref)));
// Instead, this is how it should be called
controller.getFlowManager().withParameterContextResolution(() -> controller
.getFlowManager().createParameterContext("id2", "name2", parameters, Collections.singletonList(ref)));
} finally {
purgeFlow();
}
}
private void purgeFlow() {
final ProcessGroup processGroup = controller.getFlowManager().getRootGroup();
for (final ProcessorNode procNode : processGroup.getProcessors()) {

View File

@ -30,6 +30,11 @@ import org.apache.nifi.encrypt.PropertyEncryptorFactory;
import org.apache.nifi.nar.ExtensionDiscoveringManager;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
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.provenance.MockProvenanceRepository;
import org.apache.nifi.registry.VariableRegistry;
import org.apache.nifi.registry.flow.FlowRegistryClient;
@ -45,6 +50,7 @@ import org.w3c.dom.Document;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -113,7 +119,22 @@ public class StandardFlowSerializerTest {
dummy.setComments(RAW_COMMENTS);
controller.getFlowManager().getRootGroup().addProcessor(dummy);
final ParameterContext parameterContext = new StandardParameterContext("context", "Context", ParameterReferenceManager.EMPTY, null);
final ParameterContext referencedContext = new StandardParameterContext("referenced-context", "Referenced Context", ParameterReferenceManager.EMPTY, null);
final ParameterContext referencedContext2 = new StandardParameterContext("referenced-context-2", "Referenced Context 2", ParameterReferenceManager.EMPTY, null);
final Map<String, Parameter> parameters = new HashMap<>();
final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder().name("foo").sensitive(true).build();
parameters.put("foo", new Parameter(parameterDescriptor, "value"));
parameterContext.setInheritedParameterContexts(Arrays.asList(referencedContext, referencedContext2));
parameterContext.setParameters(parameters);
controller.getFlowManager().getParameterContextManager().addParameterContext(parameterContext);
controller.getFlowManager().getParameterContextManager().addParameterContext(referencedContext);
controller.getFlowManager().getParameterContextManager().addParameterContext(referencedContext2);
controller.getFlowManager().getRootGroup().setParameterContext(parameterContext);
controller.getFlowManager().getRootGroup().setVariables(Collections.singletonMap(RAW_VARIABLE_NAME, RAW_VARIABLE_VALUE));
controller.getFlowManager().getRootGroup().setParameterContext(parameterContext);
// serialize the controller
final ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -129,6 +150,7 @@ public class StandardFlowSerializerTest {
assertTrue(serializedFlow.contains(SERIALIZED_VARIABLE_VALUE));
assertFalse(serializedFlow.contains(RAW_VARIABLE_VALUE));
assertFalse(serializedFlow.contains("\u0001"));
assertTrue(serializedFlow.contains("<inheritedParameterContextId>referenced-context</inheritedParameterContextId>"));
}
@Test

View File

@ -812,6 +812,11 @@ public class MockProcessGroup implements ProcessGroup {
return null;
}
@Override
public boolean referencesParameterContext(final ParameterContext parameterContext) {
return false;
}
@Override
public String getDefaultFlowFileExpiration() {
return defaultFlowfileExpiration;

View File

@ -94,6 +94,23 @@ public class FingerprintFactoryTest {
assertNotEquals(fp1, fp2);
}
@Test
public void testInheritedParameterContextsInFingerprint() throws IOException {
final String flowWithParameterContext = new String(getResourceBytes("/nifi/fingerprint/flow-with-parameter-context.xml"));
final String flowWithNoInheritedParameterContexts = flowWithParameterContext.replaceAll("<inheritedParameterContextId>.*?</inheritedParameterContextId>", "");
final String flowWithDifferentInheritedParamContextOrder = flowWithParameterContext
.replaceFirst("153a6266-dcd0-33e9-b5af-b8c282d25bf1", "SWAP")
.replaceFirst("253a6266-dcd0-33e9-b5af-b8c282d25bf2", "153a6266-dcd0-33e9-b5af-b8c282d25bf1")
.replaceFirst("SWAP", "253a6266-dcd0-33e9-b5af-b8c282d25bf2");
final String originalFingerprint = fingerprintFactory.createFingerprint(flowWithParameterContext.getBytes(StandardCharsets.UTF_8), null);
final String fingerprintWithNoInheritedParameterContexts = fingerprintFactory.createFingerprint(flowWithNoInheritedParameterContexts.getBytes(StandardCharsets.UTF_8), null);
final String fingerprintWithDifferentInheritedParamContextOrder = fingerprintFactory.createFingerprint(flowWithDifferentInheritedParamContextOrder.getBytes(StandardCharsets.UTF_8), null);
assertNotEquals(originalFingerprint, fingerprintWithNoInheritedParameterContexts);
assertNotEquals(originalFingerprint, fingerprintWithDifferentInheritedParamContextOrder);
}
@Test
public void testResourceValueInFingerprint() throws IOException {
final String fingerprint = fingerprintFactory.createFingerprint(getResourceBytes("/nifi/fingerprint/flow1a.xml"), null);

View File

@ -41,6 +41,7 @@ import org.apache.nifi.processor.StandardProcessContext;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -48,6 +49,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import static org.junit.Assert.assertEquals;
@ -78,6 +80,61 @@ public class ParametersIT extends FrameworkIntegrationTest {
assertEquals("unit", flowFileRecord.getAttribute("test"));
}
@Test
public void testNestedParamSubstitution_configProcessorFirst() throws ExecutionException, InterruptedException {
runParameterSubstitutionInNestedParameterContextTest((updateAttribute, parameterContext) -> {
updateAttribute.setProperties(Collections.singletonMap("test", "#{test}"));
getRootGroup().setParameterContext(parameterContext);
}, "unit");
}
@Test
public void testNestedParamSubstitution_configProcessorLast() throws ExecutionException, InterruptedException {
runParameterSubstitutionInNestedParameterContextTest((updateAttribute, parameterContext) -> {
getRootGroup().setParameterContext(parameterContext);
updateAttribute.setProperties(Collections.singletonMap("test", "#{test}"));
}, "unit");
}
@Test
public void testNestedParamSubstitution_updateParamContext() throws ExecutionException, InterruptedException {
runParameterSubstitutionInNestedParameterContextTest((updateAttribute, parameterContext) -> {
getRootGroup().setParameterContext(parameterContext);
updateAttribute.setProperties(Collections.singletonMap("test", "#{test}"));
parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "bar")));
getRootGroup().setParameterContext(parameterContext);
}, "bar");
}
public void runParameterSubstitutionInNestedParameterContextTest(BiConsumer<ProcessorNode, ParameterContext> testConsumer, String expectedValue) throws ExecutionException, InterruptedException {
final ProcessorNode generate = createProcessorNode(GenerateProcessor.class);
final ProcessorNode updateAttribute = createProcessorNode(UpdateAttributeNoEL.class);
final ProcessorNode terminate = getTerminateProcessor();
final Connection generatedFlowFileConnection = connect(generate, updateAttribute, REL_SUCCESS);
final Connection updatedAttributeConnection = connect(updateAttribute, terminate, REL_SUCCESS);
final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
parameterContext.setParameters(Collections.singletonMap("foo", new Parameter(new ParameterDescriptor.Builder().name("foo").build(), "bar")));
final ParameterContext referencedParameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context-2", referenceManager, null);
referencedParameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
parameterContext.setInheritedParameterContexts(Arrays.asList(referencedParameterContext));
testConsumer.accept(updateAttribute, parameterContext);
triggerOnce(generate);
triggerOnce(updateAttribute);
final FlowFileQueue flowFileQueue = updatedAttributeConnection.getFlowFileQueue();
final FlowFileRecord flowFileRecord = flowFileQueue.poll(Collections.emptySet());
assertEquals(expectedValue, flowFileRecord.getAttribute("test"));
}
@Test
public void testParameterSubstitutionWithinELWhenELNotSupported() throws ExecutionException, InterruptedException {
final ProcessorNode generate = createProcessorNode(GenerateProcessor.class);
@ -128,6 +185,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
assertEquals("UNIT", flowFileRecord.getAttribute("test"));
}
@Test
public void testMixAndMatchELAndParameters() throws ExecutionException, InterruptedException {
final ProcessorNode generate = createProcessorNode(GenerateProcessor.class);

View File

@ -80,6 +80,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -139,6 +140,8 @@ public class NiFiRegistryFlowMapperTest {
// verify single parameter context
assertEquals(1, versionedParameterContexts.size());
assertEquals(1, versionedParameterContexts.get("context10").getInheritedParameterContexts().size());
assertEquals("other-context", versionedParameterContexts.get("context10").getInheritedParameterContexts().get(0));
final String expectedName = innerProcessGroup.getParameterContext().getName();
verifyParameterContext(innerProcessGroup.getParameterContext(), versionedParameterContexts.get(expectedName));
@ -261,6 +264,7 @@ public class NiFiRegistryFlowMapperTest {
when(parameterContext.getName()).thenReturn("context" + (counter++));
final Map<ParameterDescriptor, Parameter> parametersMap = Maps.newHashMap();
when(parameterContext.getParameters()).thenReturn(parametersMap);
when(parameterContext.getInheritedParameterContextNames()).thenReturn(Arrays.asList("other-context"));
addParameter(parametersMap, "value" + (counter++), false);
addParameter(parametersMap, "value" + (counter++), true);

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<flowController encoding-version="1.4">
<maxTimerDrivenThreadCount>10</maxTimerDrivenThreadCount>
<maxEventDrivenThreadCount>1</maxEventDrivenThreadCount>
<registries/>
<parameterContexts>
<parameterContext>
<id>context</id>
<name>Context</name>
<description/>
<inheritedParameterContextId>referenced-context</inheritedParameterContextId>
<inheritedParameterContextId>referenced-context-2</inheritedParameterContextId>
<parameter>
<name>foo</name>
<description/>
<sensitive>true</sensitive>
<value>enc{c5e2924eeec6d395ba09090cd50c5f2cee1ba23735d7eadfedae96d0b0fd257b}</value>
</parameter>
</parameterContext>
<parameterContext>
<id>referenced-context</id>
<name>Referenced Context</name>
<description/>
</parameterContext>
</parameterContexts>
<rootGroup>
<id>2ae3cdb4-0179-1000-6ddc-ed1dca231bac</id>
<name>NiFi Flow</name>
<position x="0.0" y="0.0"/>
<comment/>
<flowfileConcurrency>UNBOUNDED</flowfileConcurrency>
<flowfileOutboundPolicy>STREAM_WHEN_AVAILABLE</flowfileOutboundPolicy>
<processor>
<id>c40fc154-ef89-48b8-82bf-ff6cc9e8f591</id>
<name>DummyScheduledProcessor</name>
<position x="0.0" y="0.0"/>
<styles/>
<comment>&lt;tagName&gt; "This" is an ' example with many characters that need to be filtered and escaped in it. &#127; &#134; </comment>
<class>org.apache.nifi.controller.DummyScheduledProcessor</class>
<maxConcurrentTasks>5</maxConcurrentTasks>
<schedulingPeriod>0 0 0 1/1 * ?</schedulingPeriod>
<penalizationPeriod>30 sec</penalizationPeriod>
<yieldPeriod>1 sec</yieldPeriod>
<bulletinLevel>WARN</bulletinLevel>
<lossTolerant>false</lossTolerant>
<scheduledState>STOPPED</scheduledState>
<schedulingStrategy>CRON_DRIVEN</schedulingStrategy>
<executionNode>ALL</executionNode>
<runDurationNanos>0</runDurationNanos>
</processor>
<variable name="Name with escape needed" value="Value with escape needed"/>
<inheritedParameterContextId>context</inheritedParameterContextId>
</rootGroup>
<controllerServices/>
<reportingTasks/>
</flowController>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<flowController encoding-version="1.4">
<maxTimerDrivenThreadCount>10</maxTimerDrivenThreadCount>
<maxEventDrivenThreadCount>1</maxEventDrivenThreadCount>
<registries/>
<parameterContexts>
<parameterContext>
<id>context</id>
<name>Context</name>
<description/>
<inheritedParameterContextId>referenced-context</inheritedParameterContextId>
<inheritedParameterContextId>referenced-context-2</inheritedParameterContextId>
<parameter>
<name>foo</name>
<description/>
<sensitive>true</sensitive>
<value>enc{c5e2924eeec6d395ba09090cd50c5f2cee1ba23735d7eadfedae96d0b0fd257b}</value>
</parameter>
</parameterContext>
<parameterContext>
<id>referenced-context</id>
<name>Referenced Context</name>
<description/>
</parameterContext>
<parameterContext>
<id>referenced-context-2</id>
<name>Referenced Context 2</name>
<description/>
</parameterContext>
</parameterContexts>
<rootGroup>
<id>2ae3cdb4-0179-1000-6ddc-ed1dca231bac</id>
<name>NiFi Flow</name>
<position x="0.0" y="0.0"/>
<comment/>
<flowfileConcurrency>UNBOUNDED</flowfileConcurrency>
<flowfileOutboundPolicy>STREAM_WHEN_AVAILABLE</flowfileOutboundPolicy>
<processor>
<id>c40fc154-ef89-48b8-82bf-ff6cc9e8f591</id>
<name>DummyScheduledProcessor</name>
<position x="0.0" y="0.0"/>
<styles/>
<comment>&lt;tagName&gt; "This" is an ' example with many characters that need to be filtered and escaped in it. &#127; &#134; </comment>
<class>org.apache.nifi.controller.DummyScheduledProcessor</class>
<maxConcurrentTasks>5</maxConcurrentTasks>
<schedulingPeriod>0 0 0 1/1 * ?</schedulingPeriod>
<penalizationPeriod>30 sec</penalizationPeriod>
<yieldPeriod>1 sec</yieldPeriod>
<bulletinLevel>WARN</bulletinLevel>
<lossTolerant>false</lossTolerant>
<scheduledState>STOPPED</scheduledState>
<schedulingStrategy>CRON_DRIVEN</schedulingStrategy>
<executionNode>ALL</executionNode>
<runDurationNanos>0</runDurationNanos>
</processor>
<variable name="Name with escape needed" value="Value with escape needed"/>
<inheritedParameterContextId>context</inheritedParameterContextId>
</rootGroup>
<controllerServices/>
<reportingTasks/>
</flowController>

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<flowController>
<maxThreadCount>15</maxThreadCount>
<parameterContexts>
<parameterContext>
<id>053a6266-dcd0-33e9-b5af-b8c282d25bf0</id>
<name>Context</name>
<description/>
<inheritedParameterContextId>153a6266-dcd0-33e9-b5af-b8c282d25bf1</inheritedParameterContextId>
<inheritedParameterContextId>253a6266-dcd0-33e9-b5af-b8c282d25bf2</inheritedParameterContextId>
<parameter>
<name>parameter</name>
<description>Parent</description>
<sensitive>false</sensitive>
<value>parent value</value>
</parameter>
</parameterContext>
<parameterContext>
<id>153a6266-dcd0-33e9-b5af-b8c282d25bf1</id>
<name>Child Context</name>
<description/>
<parameter>
<name>parameter</name>
<description>Child</description>
<sensitive>false</sensitive>
<value>child value</value>
</parameter>
</parameterContext>
<parameterContext>
<id>253a6266-dcd0-33e9-b5af-b8c282d25bf2</id>
<name>Second Child Context</name>
<description/>
<parameter>
<name>parameter</name>
<description>Second child</description>
<sensitive>false</sensitive>
<value>second child value</value>
</parameter>
</parameterContext>
</parameterContexts>
<rootGroup>
<id>e3909250-331d-420b-a9b3-cc54ad459401</id>
<name>NiFi Flow</name>
<position x="0.0" y="0.0"/>
<style/>
<comment/>
<processor>
<id>d89ada5d-35fb-44ff-83f1-4cc00b48b2df</id>
<name>GenerateFlowFile</name>
<position x="0.0" y="0.0"/>
<styles/>
<comment/>
<class>org.apache.nifi.processors.standard.GenerateFlowFile</class>
<bundle>
<group>org.apache.nifi</group>
<artifact>nifi-standard-nar</artifact>
<version>1.3.0-SNAPSHOT</version>
</bundle>
<maxConcurrentTasks>1</maxConcurrentTasks>
<schedulingPeriod>0 sec</schedulingPeriod>
<penalizationPeriod>30 sec</penalizationPeriod>
<yieldPeriod>1 sec</yieldPeriod>
<bulletinLevel>WARN</bulletinLevel>
<lossTolerant>false</lossTolerant>
<scheduledState>RUNNING</scheduledState>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<executionNode>ALL</executionNode>
<runDurationNanos>0</runDurationNanos>
<property>
<name>file.size</name>
<value>5</value>
</property>
<annotationData/>
</processor>
<processor>
<id>e520797a-dddb-4930-9034-2092d3e816a6</id>
<name>LogAttribute</name>
<position x="0.0" y="0.0"/>
<style>processor</style>
<comment/>
<class>org.apache.nifi.processors.standard.LogAttribute</class>
<maxConcurrentTasks>1</maxConcurrentTasks>
<schedulingPeriod>0 s</schedulingPeriod>
<lossTolerant>false</lossTolerant>
<running>false</running>
<annotationData/>
<autoTerminatedRelationship>success</autoTerminatedRelationship>
</processor>
<processGroup>
<id>efeece05-3934-4298-a725-658eec116470</id>
<name>Hello</name>
<position x="0.0" y="0.0"/>
<style>process-group</style>
<comment/>
<processor>
<id>34caa1d6-cf14-4ec0-9f18-12859c37d55d</id>
<name>LogAttribute</name>
<position x="0.0" y="0.0"/>
<style>processor</style>
<comment/>
<class>org.apache.nifi.processors.standard.LogAttribute</class>
<maxConcurrentTasks>1</maxConcurrentTasks>
<schedulingPeriod>0 s</schedulingPeriod>
<lossTolerant>false</lossTolerant>
<running>false</running>
<annotationData/>
</processor>
<inputPort>
<id>91fae6d8-ad95-47cf-aa83-a6dfc742b7cb</id>
<name>In</name>
<position x="0.0" y="0.0"/>
<style>input-port</style>
<comments/>
<running>false</running>
</inputPort>
<outputPort>
<id>a65695bb-a938-4d3d-bf5d-f70a335268ec</id>
<name>Out</name>
<position x="0.0" y="0.0"/>
<style>output-port</style>
<comments/>
<running>false</running>
</outputPort>
<connection>
<id>b25c3c8f-8dfe-4dda-950e-b6edfb6c99f4</id>
<name>In Connection</name>
<bendPoints/>
<labelIndex>1</labelIndex>
<zIndex>0</zIndex>
<style/>
<sourceId>91fae6d8-ad95-47cf-aa83-a6dfc742b7cb</sourceId>
<sourceGroupId>efeece05-3934-4298-a725-658eec116470</sourceGroupId>
<sourceType>INPUT_PORT</sourceType>
<destinationId>34caa1d6-cf14-4ec0-9f18-12859c37d55d</destinationId>
<destinationGroupId>efeece05-3934-4298-a725-658eec116470</destinationGroupId>
<destinationType>PROCESSOR</destinationType>
<relationship/>
<maxWorkQueueSize>0</maxWorkQueueSize>
<flowFileExpiration>0 s</flowFileExpiration>
</connection>
<connection>
<id>908afab7-8777-4acf-a807-24f684f7aa9f</id>
<name/>
<bendPoints/>
<labelIndex>1</labelIndex>
<zIndex>0</zIndex>
<style/>
<sourceId>34caa1d6-cf14-4ec0-9f18-12859c37d55d</sourceId>
<sourceGroupId>efeece05-3934-4298-a725-658eec116470</sourceGroupId>
<sourceType>PROCESSOR</sourceType>
<destinationId>a65695bb-a938-4d3d-bf5d-f70a335268ec</destinationId>
<destinationGroupId>efeece05-3934-4298-a725-658eec116470</destinationGroupId>
<destinationType>OUTPUT_PORT</destinationType>
<relationship>success</relationship>
<maxWorkQueueSize>0</maxWorkQueueSize>
<flowFileExpiration>0 s</flowFileExpiration>
</connection>
</processGroup>
<connection>
<id>03f4f5bf-baa5-47fa-9b1a-b77860d67d4f</id>
<name/>
<bendPoints/>
<labelIndex>1</labelIndex>
<zIndex>0</zIndex>
<style/>
<sourceId>d89ada5d-35fb-44ff-83f1-4cc00b48b2df</sourceId>
<sourceGroupId>e3909250-331d-420b-a9b3-cc54ad459401</sourceGroupId>
<sourceType>PROCESSOR</sourceType>
<destinationId>91fae6d8-ad95-47cf-aa83-a6dfc742b7cb</destinationId>
<destinationGroupId>efeece05-3934-4298-a725-658eec116470</destinationGroupId>
<destinationType>INPUT_PORT</destinationType>
<relationship>success</relationship>
<maxWorkQueueSize>0</maxWorkQueueSize>
<flowFileExpiration>0 s</flowFileExpiration>
</connection>
<connection>
<id>5bd05300-f03d-4511-a13f-6a36afe2bcc5</id>
<name/>
<bendPoints/>
<labelIndex>1</labelIndex>
<zIndex>0</zIndex>
<style/>
<sourceId>a65695bb-a938-4d3d-bf5d-f70a335268ec</sourceId>
<sourceGroupId>efeece05-3934-4298-a725-658eec116470</sourceGroupId>
<sourceType>OUTPUT_PORT</sourceType>
<destinationId>e520797a-dddb-4930-9034-2092d3e816a6</destinationId>
<destinationGroupId>e3909250-331d-420b-a9b3-cc54ad459401</destinationGroupId>
<destinationType>PROCESSOR</destinationType>
<relationship/>
<maxWorkQueueSize>0</maxWorkQueueSize>
<flowFileExpiration>0 s</flowFileExpiration>
</connection>
</rootGroup>
</flowController>

View File

@ -40,6 +40,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Audits processor creation/removal and configuration changes.
@ -277,6 +278,10 @@ public class ParameterContextAuditor extends NiFiAuditor {
}
});
}
if (!parameterContext.getInheritedParameterContexts().isEmpty()) {
values.put("Inherited Parameter Contexts", parameterContext.getInheritedParameterContexts()
.stream().map(pc -> pc.getIdentifier()).collect(Collectors.joining(", ")));
}
return values;
}

View File

@ -33,6 +33,7 @@ import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -160,6 +161,8 @@ public class AuthorizeParameterReference {
.map(ParameterDescriptor::getName)
.collect(Collectors.toSet());
final List<String> existingParameterContextNames = parameterContext.getInheritedParameterContextNames();
boolean requiresAddition = false;
for (final VersionedParameter versionedParameter : versionedParameterContext.getParameters()) {
final String versionedParameterName = versionedParameter.getName();
@ -169,7 +172,7 @@ public class AuthorizeParameterReference {
}
}
if (requiresAddition) {
if (requiresAddition || !existingParameterContextNames.equals(versionedParameterContext.getInheritedParameterContexts())) {
// User is required to have WRITE permission to the Parameter Context in order to add one or more parameters.
parameterContext.authorize(authorizer, RequestAction.WRITE, user);
}

View File

@ -1092,10 +1092,11 @@ public interface NiFiServiceFacade {
/**
* Returns the ParameterContextEntity for the ParameterContext with the given ID
* @param parameterContextId the ID of the Parameter Context
* @param includeInheritedParameters Whether to include inherited parameters (and thus overridden values)
* @param user the user on whose behalf the Parameter Context is being retrieved
* @return the ParameterContextEntity
*/
ParameterContextEntity getParameterContext(String parameterContextId, NiFiUser user);
ParameterContextEntity getParameterContext(String parameterContextId, boolean includeInheritedParameters, NiFiUser user);
/**
* Creates a new Parameter Context

View File

@ -101,6 +101,7 @@ import org.apache.nifi.metrics.jvm.JmxJvmMetrics;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextLookup;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.parameter.ParameterReferenceManager;
import org.apache.nifi.parameter.StandardParameterContext;
@ -1053,7 +1054,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final RevisionUpdate<ParameterContextDTO> snapshot = updateComponent(revision,
parameterContext,
() -> parameterContextDAO.updateParameterContext(parameterContextDto),
context -> dtoFactory.createParameterContextDto(context, revisionManager));
context -> dtoFactory.createParameterContextDto(context, revisionManager, false, parameterContextDAO));
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(parameterContext);
final RevisionDTO revisionDto = dtoFactory.createRevisionDTO(snapshot.getLastModification());
@ -1062,9 +1063,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
public ParameterContextEntity getParameterContext(final String parameterContextId, final NiFiUser user) {
public ParameterContextEntity getParameterContext(final String parameterContextId, final boolean includeInheritedParameters, final NiFiUser user) {
final ParameterContext parameterContext = parameterContextDAO.getParameterContext(parameterContextId);
return createParameterContextEntity(parameterContext, user);
return createParameterContextEntity(parameterContext, includeInheritedParameters, user, parameterContextDAO);
}
@Override
@ -1072,7 +1073,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Set<ParameterContextEntity> entities = parameterContextDAO.getParameterContexts().stream()
.map(context -> createParameterContextEntity(context, user))
.map(context -> createParameterContextEntity(context, false, user, parameterContextDAO))
.collect(Collectors.toSet());
return entities;
@ -1100,10 +1101,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return parameterContext;
}
private ParameterContextEntity createParameterContextEntity(final ParameterContext parameterContext, final NiFiUser user) {
private ParameterContextEntity createParameterContextEntity(final ParameterContext parameterContext, final boolean includeInheritedParameters, final NiFiUser user,
final ParameterContextLookup parameterContextLookup) {
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(parameterContext, user);
final RevisionDTO revisionDto = dtoFactory.createRevisionDTO(revisionManager.getRevision(parameterContext.getIdentifier()));
final ParameterContextDTO parameterContextDto = dtoFactory.createParameterContextDto(parameterContext, revisionManager);
final ParameterContextDTO parameterContextDto = dtoFactory.createParameterContextDto(parameterContext, revisionManager, includeInheritedParameters, parameterContextLookup);
final ParameterContextEntity entity = entityFactory.createParameterContextEntity(parameterContextDto, revisionDto, permissions);
return entity;
}
@ -1113,7 +1115,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ParameterContext parameterContext = parameterContextDAO.getParameterContext(parameterContextDto.getId());
final Set<ProcessGroup> boundProcessGroups = parameterContext.getParameterReferenceManager().getProcessGroupsBound(parameterContext);
final ParameterContext updatedParameterContext = new StandardParameterContext(parameterContext.getIdentifier(), parameterContext.getName(), ParameterReferenceManager.EMPTY, null);
final ParameterContext updatedParameterContext = new StandardParameterContext(parameterContext.getIdentifier(), parameterContext.getName(),
ParameterReferenceManager.EMPTY, null);
final Map<String, Parameter> parameters = new HashMap<>();
parameterContextDto.getParameters().stream()
.map(ParameterEntity::getParameter)
@ -1186,7 +1189,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
// save the update
controllerFacade.save();
final ParameterContextDTO dto = dtoFactory.createParameterContextDto(parameterContext, revisionManager);
final ParameterContextDTO dto = dtoFactory.createParameterContextDto(parameterContext, revisionManager, false, parameterContextDAO);
final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
return new StandardRevisionUpdate<>(dto, lastMod);
});
@ -1211,7 +1214,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
parameterContext.getResource(),
() -> parameterContextDAO.deleteParameterContext(parameterContextId),
true,
dtoFactory.createParameterContextDto(parameterContext, revisionManager));
dtoFactory.createParameterContextDto(parameterContext, revisionManager, false, parameterContextDAO));
return entityFactory.createParameterContextEntity(snapshot, null, permissions);
@ -1286,8 +1289,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
private Set<AffectedComponentEntity> getComponentsAffectedByParameterContextUpdate(final ParameterContextDTO parameterContextDto, final boolean includeInactive) {
final ProcessGroup rootGroup = processGroupDAO.getProcessGroup("root");
final ParameterContext parameterContext = parameterContextDAO.getParameterContext(parameterContextDto.getId());
final List<ProcessGroup> groupsReferencingParameterContext = rootGroup.findAllProcessGroups(
group -> group.getParameterContext() != null && group.getParameterContext().getIdentifier().equals(parameterContextDto.getId()));
group -> group.getParameterContext() != null && (group.getParameterContext().getIdentifier().equals(parameterContextDto.getId())
|| group.getParameterContext().inheritsFrom(parameterContext.getIdentifier())));
final Set<String> updatedParameterNames = getUpdatedParameterNames(parameterContextDto);

View File

@ -147,7 +147,10 @@ public class ParameterContextResource extends ApplicationResource {
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
})
public Response getParameterContext(@ApiParam("The ID of the Parameter Context") @PathParam("id") final String parameterContextId) {
public Response getParameterContext(@ApiParam("The ID of the Parameter Context") @PathParam("id") final String parameterContextId,
@ApiParam("Whether or not to include inherited parameters from other parameter contexts, and therefore also overridden values. " +
"If true, the result will be the 'effective' parameter context.") @QueryParam("includeInheritedParameters")
@DefaultValue("false") final boolean includeInheritedParameters) {
// authorize access
authorizeReadParameterContext(parameterContextId);
@ -156,7 +159,7 @@ public class ParameterContextResource extends ApplicationResource {
}
// get the specified parameter context
final ParameterContextEntity entity = serviceFacade.getParameterContext(parameterContextId, NiFiUserUtils.getNiFiUser());
final ParameterContextEntity entity = serviceFacade.getParameterContext(parameterContextId, includeInheritedParameters, NiFiUserUtils.getNiFiUser());
entity.setUri(generateResourceUri("parameter-contexts", entity.getId()));
// generate the response
@ -171,7 +174,8 @@ public class ParameterContextResource extends ApplicationResource {
value = "Create a Parameter Context",
response = ParameterContextEntity.class,
authorizations = {
@Authorization(value = "Write - /parameter-contexts")
@Authorization(value = "Write - /parameter-contexts"),
@Authorization(value = "Read - for every inherited parameter context")
})
@ApiResponses(value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ -315,7 +319,9 @@ public class ParameterContextResource extends ApplicationResource {
@Authorization(value = "Read - /parameter-contexts/{parameterContextId}"),
@Authorization(value = "Write - /parameter-contexts/{parameterContextId}"),
@Authorization(value = "Read - for every component that is affected by the update"),
@Authorization(value = "Write - for every component that is affected by the update")
@Authorization(value = "Write - for every component that is affected by the update"),
@Authorization(value = "Read - for every currently inherited parameter context"),
@Authorization(value = "Read - for any new inherited parameter context")
})
@ApiResponses(value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ -400,11 +406,13 @@ public class ParameterContextResource extends ApplicationResource {
}
private void validateParameterNames(final ParameterContextDTO parameterContextDto) {
for (final ParameterEntity entity : parameterContextDto.getParameters()) {
final String parameterName = entity.getParameter().getName();
if (!isLegalParameterName(parameterName)) {
throw new IllegalArgumentException("Request contains an illegal Parameter Name (" + parameterName + "). Parameter names may only include letters, numbers, spaces, and the special " +
"characters .-_");
if (parameterContextDto.getParameters() != null) {
for (final ParameterEntity entity : parameterContextDto.getParameters()) {
final String parameterName = entity.getParameter().getName();
if (!isLegalParameterName(parameterName)) {
throw new IllegalArgumentException("Request contains an illegal Parameter Name (" + parameterName
+ "). Parameter names may only include letters, numbers, spaces, and the special characters .-_");
}
}
}
}
@ -576,7 +584,7 @@ public class ParameterContextResource extends ApplicationResource {
parameterContext.authorize(authorizer, RequestAction.READ, user);
parameterContext.authorize(authorizer, RequestAction.WRITE, user);
final ParameterContextEntity contextEntity = serviceFacade.getParameterContext(parameterContextId, user);
final ParameterContextEntity contextEntity = serviceFacade.getParameterContext(parameterContextId, false, user);
for (final ProcessGroupEntity boundGroupEntity : contextEntity.getComponent().getBoundProcessGroups()) {
final String groupId = boundGroupEntity.getId();
final Authorizable groupAuthorizable = lookup.getProcessGroup(groupId).getAuthorizable();
@ -660,7 +668,7 @@ public class ParameterContextResource extends ApplicationResource {
}
private void authorizeReferencingComponents(final String parameterContextId, final AuthorizableLookup lookup, final NiFiUser user) {
final ParameterContextEntity context = serviceFacade.getParameterContext(parameterContextId, NiFiUserUtils.getNiFiUser());
final ParameterContextEntity context = serviceFacade.getParameterContext(parameterContextId, false, NiFiUserUtils.getNiFiUser());
for (final ParameterEntity parameterEntity : context.getComponent().getParameters()) {
final ParameterDTO dto = parameterEntity.getParameter();
@ -946,7 +954,7 @@ public class ParameterContextResource extends ApplicationResource {
throw new LifecycleManagementException("Failed to update Flow on all nodes in cluster due to " + explanation);
}
return serviceFacade.getParameterContext(updatedContext.getId(), user);
return serviceFacade.getParameterContext(updatedContext.getId(), false, user);
} else {
serviceFacade.verifyUpdateParameterContext(updatedContext.getComponent(), true);
return serviceFacade.updateParameterContext(revision, updatedContext.getComponent());
@ -965,7 +973,11 @@ public class ParameterContextResource extends ApplicationResource {
private <T> T getResponseEntity(final NodeResponse nodeResponse, final Class<T> clazz) {
T entity = (T) nodeResponse.getUpdatedEntity();
if (entity == null) {
entity = nodeResponse.getClientResponse().readEntity(clazz);
if (nodeResponse.getClientResponse() != null) {
entity = nodeResponse.getClientResponse().readEntity(clazz);
} else {
entity = (T) nodeResponse.getThrowable().toString();
}
}
return entity;
}
@ -1188,7 +1200,7 @@ public class ParameterContextResource extends ApplicationResource {
updateRequestDto.setReferencingComponents(new HashSet<>(affectedComponents.values()));
// Populate the Affected Components
final ParameterContextEntity contextEntity = serviceFacade.getParameterContext(asyncRequest.getComponentId(), NiFiUserUtils.getNiFiUser());
final ParameterContextEntity contextEntity = serviceFacade.getParameterContext(asyncRequest.getComponentId(), false, NiFiUserUtils.getNiFiUser());
final ParameterContextUpdateRequestEntity updateRequestEntity = new ParameterContextUpdateRequestEntity();
// If the request is complete, include the new representation of the Parameter Context along with its new Revision. Otherwise, do not include the information, since it is 'stale'

View File

@ -128,6 +128,7 @@ import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextLookup;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.parameter.ParameterReferenceManager;
import org.apache.nifi.processor.Processor;
@ -1448,7 +1449,8 @@ public final class DtoFactory {
return dto;
}
public ParameterContextDTO createParameterContextDto(final ParameterContext parameterContext, final RevisionManager revisionManager) {
public ParameterContextDTO createParameterContextDto(final ParameterContext parameterContext, final RevisionManager revisionManager,
final boolean includeInheritedParameters, final ParameterContextLookup parameterContextLookup) {
final ParameterContextDTO dto = new ParameterContextDTO();
dto.setId(parameterContext.getIdentifier());
dto.setName(parameterContext.getName());
@ -1465,16 +1467,27 @@ public final class DtoFactory {
dto.setBoundProcessGroups(boundGroups);
final Set<ParameterEntity> parameterEntities = new LinkedHashSet<>();
for (final Parameter parameter : parameterContext.getParameters().values()) {
parameterEntities.add(createParameterEntity(parameterContext, parameter, revisionManager));
final Map<ParameterDescriptor, Parameter> parameters = includeInheritedParameters ? parameterContext.getEffectiveParameters()
: parameterContext.getParameters();
for (final Parameter parameter : parameters.values()) {
parameterEntities.add(createParameterEntity(parameterContext, parameter, revisionManager, parameterContextLookup));
}
final List<ParameterContextReferenceEntity> parameterContextRefs = new ArrayList<>();
if (parameterContext.getInheritedParameterContexts() != null) {
parameterContextRefs.addAll(parameterContext.getInheritedParameterContexts().stream()
.map(pc -> entityFactory.createParameterReferenceEntity(createParameterContextReference(pc), createPermissionsDto(pc)))
.collect(Collectors.toList()));
}
dto.setInheritedParameterContexts(parameterContextRefs);
dto.setParameters(parameterEntities);
return dto;
}
public ParameterEntity createParameterEntity(final ParameterContext parameterContext, final Parameter parameter, final RevisionManager revisionManager) {
final ParameterDTO dto = createParameterDto(parameterContext, parameter, revisionManager);
public ParameterEntity createParameterEntity(final ParameterContext parameterContext, final Parameter parameter, final RevisionManager revisionManager,
final ParameterContextLookup parameterContextLookup) {
final ParameterDTO dto = createParameterDto(parameterContext, parameter, revisionManager, parameterContextLookup);
final ParameterEntity entity = new ParameterEntity();
entity.setParameter(dto);
@ -1484,7 +1497,8 @@ public final class DtoFactory {
return entity;
}
public ParameterDTO createParameterDto(final ParameterContext parameterContext, final Parameter parameter, final RevisionManager revisionManager) {
public ParameterDTO createParameterDto(final ParameterContext parameterContext, final Parameter parameter,
final RevisionManager revisionManager, final ParameterContextLookup parameterContextLookup) {
final ParameterDescriptor descriptor = parameter.getDescriptor();
final ParameterDTO dto = new ParameterDTO();
@ -1504,6 +1518,11 @@ public final class DtoFactory {
final Set<AffectedComponentEntity> referencingComponentEntities = createAffectedComponentEntities(referencingComponents, revisionManager);
dto.setReferencingComponents(referencingComponentEntities);
final ParameterContext containingParameterContext = (parameter.getParameterContextId() == null)
? parameterContext : parameterContextLookup.getParameterContext(parameter.getParameterContextId());
ParameterContextReferenceDTO refDto = createParameterContextReference(containingParameterContext);
dto.setParameterContext(entityFactory.createParameterReferenceEntity(refDto, createPermissionsDto(containingParameterContext)));
return dto;
}

View File

@ -17,18 +17,12 @@
package org.apache.nifi.web.dao;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextLookup;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import java.util.Set;
public interface ParameterContextDAO {
/**
* Determines if the specified parameter context exists.
*
* @param parameterContextId id
* @return true if parameter context exists
*/
boolean hasParameterContext(String parameterContextId);
public interface ParameterContextDAO extends ParameterContextLookup {
/**
* Determines whether this parameter context can be created.
@ -45,14 +39,6 @@ public interface ParameterContextDAO {
*/
ParameterContext createParameterContext(ParameterContextDTO parameterContextDto);
/**
* Gets the specified parameter context.
*
* @param parameterContextId the id of the parameter context
* @return the parameter context
*/
ParameterContext getParameterContext(String parameterContextId);
/**
* Gets all of the parameter contexts.
*

View File

@ -16,6 +16,10 @@
*/
package org.apache.nifi.web.dao.impl;
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;
@ -32,19 +36,25 @@ 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;
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.apache.nifi.web.dao.ParameterContextDAO;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class StandardParameterContextDAO implements ParameterContextDAO {
private FlowManager flowManager;
private Authorizer authorizer;
@Override
public boolean hasParameterContext(final String parameterContextId) {
@ -54,16 +64,76 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
@Override
public void verifyCreate(final ParameterContextDTO parameterContextDto) {
verifyNoNamingConflict(parameterContextDto.getName());
verifyInheritedParameterContextRefs(parameterContextDto);
}
private void verifyInheritedParameterContextRefs(final ParameterContextDTO parameterContextDto) {
final List<ParameterContextReferenceEntity> inheritedParameterContexts = parameterContextDto.getInheritedParameterContexts();
if (inheritedParameterContexts != null) {
resolveInheritedParameterContexts(parameterContextDto);
// This will throw an exception if one is not found
inheritedParameterContexts.stream().forEach(entity -> flowManager.getParameterContextManager()
.getParameterContext(entity.getComponent().getId()));
}
authorizeReferences(parameterContextDto);
}
@Override
public ParameterContext createParameterContext(final ParameterContextDTO parameterContextDto) {
final Map<String, Parameter> parameters = getParameters(parameterContextDto, null);
final ParameterContext parameterContext = flowManager.createParameterContext(parameterContextDto.getId(), parameterContextDto.getName(), parameters);
if (parameterContextDto.getDescription() != null) {
parameterContext.setDescription(parameterContextDto.getDescription());
resolveInheritedParameterContexts(parameterContextDto);
final AtomicReference<ParameterContext> parameterContextReference = new AtomicReference<>();
flowManager.withParameterContextResolution(() -> {
final ParameterContext parameterContext = flowManager.createParameterContext(parameterContextDto.getId(), parameterContextDto.getName(),
parameters, parameterContextDto.getInheritedParameterContexts());
if (parameterContextDto.getDescription() != null) {
parameterContext.setDescription(parameterContextDto.getDescription());
}
parameterContextReference.set(parameterContext);
});
return parameterContextReference.get();
}
private void authorizeReferences(final ParameterContextDTO parameterContextDto) {
final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser();
if (parameterContextDto.getInheritedParameterContexts() != null) {
for (final ParameterContextReferenceEntity ref : parameterContextDto.getInheritedParameterContexts()) {
final ParameterContext parameterContext = getParameterContext(ref.getComponent().getId());
parameterContext.authorize(authorizer, RequestAction.READ, nifiUser);
}
}
}
private void resolveInheritedParameterContexts(final ParameterContextDTO parameterContextDto) {
final List<ParameterContextReferenceEntity> inheritedParameterContexts = parameterContextDto.getInheritedParameterContexts();
if (inheritedParameterContexts == null || inheritedParameterContexts.isEmpty()) {
return;
}
final Map<String, ParameterContext> paramContextNameMap = flowManager.getParameterContextManager().getParameterContextNameMapping();
for (final ParameterContextReferenceEntity ref : inheritedParameterContexts) {
if (ref.getComponent() == null || (ref.getComponent().getId() == null && ref.getComponent().getName() == null)) {
throw new IllegalStateException(String.format("Could not resolve inherited parameter context references in Parameter Context [%s]",
parameterContextDto.getName()));
}
final ParameterContextReferenceDTO refDto = ref.getComponent();
if (refDto.getId() != null) {
continue;
}
// If resolving by name only, look up the ids
final ParameterContext resolvedParameterContext = paramContextNameMap.get(refDto.getName());
if (resolvedParameterContext == null) {
throw new IllegalStateException(String.format("Parameter Context [%s] references missing inherited Parameter Context [%s]",
parameterContextDto.getName(), refDto.getName()));
}
ref.setId(resolvedParameterContext.getIdentifier());
ref.getComponent().setId(resolvedParameterContext.getIdentifier());
}
return parameterContext;
}
private Map<String, Parameter> getParameters(final ParameterContextDTO parameterContextDto, final ParameterContext context) {
@ -149,12 +219,28 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
context.setParameters(parameters);
}
if (parameterContextDto.getInheritedParameterContexts() != null) {
final List<ParameterContext> inheritedParameterContexts = getInheritedParameterContexts(parameterContextDto);
context.setInheritedParameterContexts(inheritedParameterContexts);
}
return context;
}
private List<ParameterContext> getInheritedParameterContexts(final ParameterContextDTO parameterContextDto) {
resolveInheritedParameterContexts(parameterContextDto);
final List<ParameterContext> inheritedParameterContexts = new ArrayList<>();
inheritedParameterContexts.addAll(parameterContextDto.getInheritedParameterContexts().stream()
.map(entity -> flowManager.getParameterContextManager().getParameterContext(entity.getComponent().getId()))
.collect(Collectors.toList()));
return inheritedParameterContexts;
}
@Override
public void verifyUpdate(final ParameterContextDTO parameterContextDto, final boolean verifyComponentStates) {
verifyNoNamingConflict(parameterContextDto.getName(), parameterContextDto.getId());
verifyInheritedParameterContextRefs(parameterContextDto);
final ParameterContext currentContext = getParameterContext(parameterContextDto.getId());
for (final ParameterEntity parameterEntity : parameterContextDto.getParameters()) {
@ -208,8 +294,21 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
}
if (active && (verifyComponentStates || parameterDeletion)) {
throw new IllegalStateException("Cannot update Parameter Context " + contextName + " because it has Parameters that are being referenced by a " +
activeExplanation + ".");
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 + ".");
}
}
}
}
@ -268,6 +367,13 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
}
}
}
for (final ParameterContext parameterContext : flowManager.getParameterContextManager().getParameterContexts()) {
if (parameterContext.getInheritedParameterContexts().stream().anyMatch(pc -> pc.getIdentifier().equals(parameterContextId))) {
throw new IllegalStateException(String.format("Cannot delete Parameter Context with ID [%s] because it is referenced by at least one Parameter Context [%s]",
parameterContextId, parameterContext.getName()));
}
}
}
@Override
@ -289,4 +395,8 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
final ProcessGroup rootGroup = flowManager.getRootGroup();
return rootGroup.findAllProcessGroups(group -> group.getParameterContext() != null && group.getParameterContext().getIdentifier().equals(parameterContextId));
}
public void setAuthorizer(final Authorizer authorizer) {
this.authorizer = authorizer;
}
}

View File

@ -271,6 +271,7 @@
</bean>
<bean id="parameterContextDAO" class="org.apache.nifi.web.dao.impl.StandardParameterContextDAO">
<property name="flowController" ref="flowController" />
<property name="authorizer" ref="authorizer" />
</bean>
<bean id="controllerSearchService" class="org.apache.nifi.web.controller.ControllerSearchService">
<property name="flowController" ref="flowController" />

View File

@ -71,8 +71,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Arrays;
import java.util.List;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

View File

@ -1538,6 +1538,9 @@
$.ajax({
type: 'GET',
url: '../nifi-api/parameter-contexts/' + encodeURIComponent(parameterContext.id),
data: {
includeInheritedParameters: 'true'
},
dataType: 'json'
}).done(function (response) {
var sensitive = nfCommon.isSensitiveProperty(propertyDescriptor);

View File

@ -174,6 +174,13 @@
var sortParameters = function (sortDetails, data) {
// defines a function for sorting
var comparer = function (a, b) {
// direct parameters always come above inherited ones
if (a.isInherited === false && b.isInherited === true) {
return -1;
}
if (a.isInherited === true && b.isInherited === false) {
return 1;
}
if (sortDetails.columnId === 'name') {
var aString = _.get(a, '[' + sortDetails.columnId + ']', '');
var bString = _.get(b, '[' + sortDetails.columnId + ']', '');
@ -544,6 +551,8 @@
// set to pending
$('<div class="referencing-component-container"><span class="unset">Pending Apply</span></div>').appendTo(parameterReferencingComponentsContainer);
} else {
$('#parameter-referencing-components-container').empty();
// bin the referencing components according to their type
$.each(referencingComponents, function (_, referencingComponentEntity) {
if (referencingComponentEntity.permissions.canRead === true && referencingComponentEntity.permissions.canWrite === true) {
@ -809,6 +818,13 @@
if (isValid) {
var permissions = {
canRead: true,
canWrite: true
};
var parameterContext = {
permissions: permissions
};
var parameter = _.extend({}, param, {
id: _.defaultTo(param.id, parameterCount),
hidden: false,
@ -819,11 +835,18 @@
isModified: true,
hasValueChanged: false,
isNew: true,
isInherited: false,
parameterContext: parameterContext
});
if (_.isNil(param.id)) {
// add a row for the new parameter
parameterData.addItem(parameter);
var matchingParameter = _.find(parameterData.getItems(), {name: parameter.name});
if (_.isNil(matchingParameter)) {
// add a row for the new parameter
parameterData.addItem(parameter);
} else {
parameterData.updateItem(matchingParameter.id, parameter);
}
} else {
parameterData.updateItem(param.id, parameter);
}
@ -918,8 +941,10 @@
// validate the parameter is not a duplicate
var matchingParameter = _.find(existingParameters, {name: parameter.name});
// Valid if no duplicate is found or it is edit mode and a matching parameter was found
if (_.isNil(matchingParameter) || (editMode === true && !_.isNil(matchingParameter))) {
// Valid if no duplicate is found or it is edit mode and a matching parameter was found, or it's
// an inherited parameter
if (_.isNil(matchingParameter) || (editMode === true && !_.isNil(matchingParameter))
|| matchingParameter.isInherited === true) {
return true;
} else {
var matchingParamIsHidden = _.get(matchingParameter, 'hidden', false);
@ -1009,7 +1034,9 @@
hasValueChanged: serializedParam.hasValueChanged,
hasDescriptionChanged: serializedParam.hasDescriptionChanged,
value: serializedParam.value,
isModified: serializedParam.hasValueChanged || serializedParam.hasDescriptionChanged
isModified: serializedParam.hasValueChanged || serializedParam.hasDescriptionChanged,
isInherited: originalParameter.isInherited,
parameterContext: originalParameter.parameterContext
});
// update row for the parameter
@ -1448,6 +1475,10 @@
var parameters = [];
$.each(parameterContext.component.parameters, function (i, parameterEntity) {
var containingParameterContext = {
id: parameterEntity.parameter.parameterContext.component.id,
permissions: parameterEntity.parameter.parameterContext.permissions
};
var parameter = {
id: parameterCount++,
hidden: false,
@ -1462,7 +1493,9 @@
previousValue: parameterEntity.parameter.value,
previousDescription: parameterEntity.parameter.description,
isEditable: _.defaultTo(readOnly, false) ? false : parameterEntity.canWrite,
referencingComponents: parameterEntity.parameter.referencingComponents
referencingComponents: parameterEntity.parameter.referencingComponents,
parameterContext: containingParameterContext,
isInherited: (containingParameterContext.id !== parameterContext.component.id)
};
parameters.push({
@ -1584,7 +1617,9 @@
var parameterActionFormatter = function (row, cell, value, columnDef, dataContext) {
var markup = '';
if (dataContext.isEditable === true) {
if (dataContext.isInherited === true && dataContext.parameterContext.permissions.canRead) {
markup += '<div title="Go To" class="pointer go-to-parameter fa fa-long-arrow-right"></div>';
} else if (dataContext.isEditable === true) {
markup += '<div title="Edit" class="edit-parameter pointer fa fa-pencil"></div>';
markup += '<div title="Delete" class="delete-parameter pointer fa fa-trash"></div>';
}
@ -1789,7 +1824,15 @@
// prevents standard edit logic
e.stopImmediatePropagation();
}
} else if (target.hasClass('go-to-parameter')) {
if (parameter.parameterContext.permissions.canRead === true) {
// close the dialog since we are sending the user to the parameter context
close();
resetDialog();
nfParameterContexts.showParameterContext(parameter.parameterContext.id, parameter.parameterContext.permissions.canWrite === false, parameter.name);
}
}
}
});
parametersGrid.onSelectedRowsChanged.subscribe(function (e, args) {
@ -2490,6 +2533,9 @@
var reloadContext = $.ajax({
type: 'GET',
url: config.urls.parameterContexts + '/' + encodeURIComponent(id),
data: {
includeInheritedParameters: 'true'
},
dataType: 'json'
});
@ -2582,6 +2628,7 @@
}
}];
// show the context
$('#parameter-context-dialog')
.modal('setHeaderText', canWrite ? 'Update Parameter Context' : 'View Parameter Context')
@ -2660,6 +2707,9 @@
var getContext = $.ajax({
type: 'GET',
url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextId),
data: {
includeInheritedParameters: 'true'
},
dataType: 'json'
});

View File

@ -639,6 +639,9 @@
$.ajax({
type: 'GET',
url: '../nifi-api/parameter-contexts/' + encodeURIComponent(parameterContext.id),
data: {
includeInheritedParameters: 'true'
},
dataType: 'json'
}).done(function (response) {
var sensitive = nfCommon.isSensitiveProperty(propertyDescriptor);

View File

@ -18,6 +18,7 @@ package org.apache.nifi.registry.flow;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import java.util.Set;
public class VersionedParameterContext {
@ -25,6 +26,7 @@ public class VersionedParameterContext {
private String name;
private String description;
private Set<VersionedParameter> parameters;
private List<String> inheritedParameterContexts;
@ApiModelProperty("The name of the context")
public String getName() {
@ -52,4 +54,13 @@ public class VersionedParameterContext {
public void setParameters(Set<VersionedParameter> parameters) {
this.parameters = parameters;
}
@ApiModelProperty("The names of additional parameter contexts from which to inherit parameters")
public List<String> getInheritedParameterContexts() {
return inheritedParameterContexts;
}
public void setInheritedParameterContexts(List<String> parameterContextNames) {
this.inheritedParameterContexts = parameterContextNames;
}
}

View File

@ -176,8 +176,7 @@ public class StandardStatelessEngine implements StatelessEngine<VersionedFlowSna
// Map existing parameter contexts by name
final Set<ParameterContext> parameterContexts = flowManager.getParameterContextManager().getParameterContexts();
final Map<String, ParameterContext> parameterContextMap = parameterContexts.stream()
.collect(Collectors.toMap(ParameterContext::getName, context -> context));
final Map<String, ParameterContext> parameterContextMap = flowManager.getParameterContextManager().getParameterContextNameMapping();
// Update Parameters to match those that are provided in the flow configuration, plus those overrides provided
final List<ParameterContextDefinition> parameterContextDefinitions = dataflowDefinition.getParameterContexts();

View File

@ -173,10 +173,16 @@ public class NiFiClientUtil {
}
public ParameterContextEntity createParameterContextEntity(final String name, final String description, final Set<ParameterEntity> parameters) {
return createParameterContextEntity(name, description, parameters, Collections.emptyList());
}
public ParameterContextEntity createParameterContextEntity(final String name, final String description, final Set<ParameterEntity> parameters,
final List<ParameterContextReferenceEntity> inheritedParameterContexts) {
final ParameterContextDTO contextDto = new ParameterContextDTO();
contextDto.setName(name);
contextDto.setDescription(description);
contextDto.setParameters(parameters);
contextDto.setInheritedParameterContexts(inheritedParameterContexts);
final ParameterContextEntity entity = new ParameterContextEntity();
entity.setComponent(contextDto);

View File

@ -215,7 +215,7 @@ public class JoinClusterWithDifferentFlow extends NiFiSystemIT {
currentState = generateFlowFileEntity.getComponent().getState();
}
final ParameterContextDTO contextDto = node2Client.getParamContextClient().getParamContext(paramContextReference.getId()).getComponent();
final ParameterContextDTO contextDto = node2Client.getParamContextClient().getParamContext(paramContextReference.getId(), false).getComponent();
assertEquals(2, contextDto.getBoundProcessGroups().size());
assertEquals(1, contextDto.getParameters().size());
final ParameterEntity parameterEntity = contextDto.getParameters().iterator().next();

View File

@ -20,11 +20,13 @@ import org.apache.nifi.tests.system.NiFiSystemIT;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
import org.apache.nifi.toolkit.cli.impl.client.nifi.ParamContextClient;
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.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterContextUpdateRequestEntity;
import org.apache.nifi.web.api.entity.ParameterEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
@ -34,6 +36,7 @@ import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -61,7 +64,7 @@ public class ParameterContextIT extends NiFiSystemIT {
assertSingleFooCreation(returned);
final String contextId = returned.getId();
final ParameterContextEntity fetched = paramContextClient.getParamContext(contextId);
final ParameterContextEntity fetched = paramContextClient.getParamContext(contextId, false);
assertSingleFooCreation(fetched);
}
@ -89,7 +92,7 @@ public class ParameterContextIT extends NiFiSystemIT {
assertSensitiveParametersNotReturned(returned);
final String contextId = returned.getId();
final ParameterContextEntity fetched = paramContextClient.getParamContext(contextId);
final ParameterContextEntity fetched = paramContextClient.getParamContext(contextId, false);
assertSensitiveParametersNotReturned(fetched);
}
@ -154,6 +157,30 @@ public class ParameterContextIT extends NiFiSystemIT {
waitForValidProcessor(processorId);
}
@Test
public void testValidationWithNestedParameterContexts() throws NiFiClientException, IOException, InterruptedException {
final ProcessorEntity generate = getClientUtil().createProcessor("GenerateFlowFile");
getClientUtil().updateProcessorProperties(generate, Collections.singletonMap("File Size", "#{foo}"));
getClientUtil().setAutoTerminatedRelationships(generate, "success");
final String processorId = generate.getId();
waitForInvalidProcessor(processorId);
final ParameterEntity fooKB = createParameterEntity("foo", null, false, "1 KB");
final Set<ParameterEntity> parameters = new HashSet<>();
parameters.add(fooKB);
final ParameterContextEntity contextEntity = createParameterContextEntity(getTestName(), null, parameters);
final ParameterContextEntity createdContextEntity = getNifiClient().getParamContextClient().createParamContext(contextEntity);
final ParameterContextEntity parentContextEntity = createParameterContextEntity(getTestName() + " Parent", null,
null, Arrays.asList(createdContextEntity));
final ParameterContextEntity createdParentContextEntity = getNifiClient().getParamContextClient().createParamContext(parentContextEntity);
setParameterContext("root", createdParentContextEntity);
waitForValidProcessor(processorId);
}
@Test(timeout=30000)
public void testValidationWithRequiredPropertiesAndNoDefault() throws NiFiClientException, IOException, InterruptedException {
final ProcessorEntity generate = getClientUtil().createProcessor("DependOnProperties");
@ -609,8 +636,25 @@ public class ParameterContextIT extends NiFiSystemIT {
return getClientUtil().createParameterEntity(name, description, sensitive, value);
}
public ParameterContextEntity createParameterContextEntity(final String name, final String description, final Set<ParameterEntity> parameters,
final List<ParameterContextEntity> parameterContextRefs) {
final List<ParameterContextReferenceEntity> refs = new ArrayList<>();
if (parameterContextRefs != null) {
refs.addAll(parameterContextRefs.stream().map(pce -> {
ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity();
ref.setId(pce.getId());
ParameterContextReferenceDTO refDto = new ParameterContextReferenceDTO();
refDto.setId(pce.getComponent().getId());
refDto.setName(pce.getComponent().getName());
ref.setComponent(refDto);
return ref;
}).collect(Collectors.toList()));
}
return getClientUtil().createParameterContextEntity(name, description, parameters, refs);
}
public ParameterContextEntity createParameterContextEntity(final String name, final String description, final Set<ParameterEntity> parameters) {
return getClientUtil().createParameterContextEntity(name, description, parameters);
return createParameterContextEntity(name, description, parameters, Collections.emptyList());
}
private ProcessGroupEntity setParameterContext(final String groupId, final ParameterContextEntity parameterContext) throws NiFiClientException, IOException {

View File

@ -26,7 +26,7 @@ public interface ParamContextClient {
ParameterContextsEntity getParamContexts() throws NiFiClientException, IOException;
ParameterContextEntity getParamContext(String id) throws NiFiClientException, IOException;
ParameterContextEntity getParamContext(String id, boolean includeInheritedParameters) throws NiFiClientException, IOException;
ParameterContextEntity createParamContext(ParameterContextEntity paramContext) throws NiFiClientException, IOException;

View File

@ -53,14 +53,15 @@ public class JerseyParamContextClient extends AbstractJerseyClient implements Pa
}
@Override
public ParameterContextEntity getParamContext(final String id) throws NiFiClientException, IOException {
public ParameterContextEntity getParamContext(final String id, final boolean includeInheritedParameters) throws NiFiClientException, IOException {
if (StringUtils.isBlank(id)) {
throw new IllegalArgumentException("Parameter context id cannot be null or blank");
}
return executeAction("Error retrieving parameter context", () -> {
final WebTarget target = paramContextTarget.path("{id}")
.resolveTemplate("id", id);
.resolveTemplate("id", id)
.queryParam("includeInheritedParameters", String.valueOf(includeInheritedParameters));
return getRequestBuilder(target).get(ParameterContextEntity.class);
});
}

View File

@ -115,6 +115,8 @@ public enum CommandOption {
PARAM_CONTEXT_ID("pcid", "paramContextId", "The id of a parameter context", true),
PARAM_CONTEXT_NAME("pcn", "paramContextName", "The name of a parameter context", true),
PARAM_CONTEXT_DESC("pcd", "paramContextDescription", "The description of a parameter context", true),
PARAM_CONTEXT_INCLUDE_INHERITED("pcin", "paramContextIncludeInherited", "Indicates that all inherited parameters should be included", false),
PARAM_CONTEXT_INHERITED_IDS("pcii", "paramContextInheritedIds", "A comma-separated list of parameter context IDs to inherit", true),
PARAM_NAME("pn", "paramName", "The name of the parameter", true),
PARAM_DESC("pd", "paramDescription", "The description of the parameter", true),

View File

@ -42,12 +42,14 @@ import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.GetNode;
import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.GetNodes;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.ExportParamContext;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.ImportParamContext;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.RemoveInheritedParamContexts;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.SetParam;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.DeleteParam;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.DeleteParamContext;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.GetParamContext;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.ListParamContexts;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.MergeParamContext;
import org.apache.nifi.toolkit.cli.impl.command.nifi.params.SetInheritedParamContexts;
import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGChangeVersion;
import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGCreateControllerService;
import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGDisableControllerServices;
@ -154,6 +156,8 @@ public class NiFiCommandGroup extends AbstractCommandGroup {
commands.add(new GetParamContext());
commands.add(new CreateParamContext());
commands.add(new DeleteParamContext());
commands.add(new SetInheritedParamContexts());
commands.add(new RemoveInheritedParamContexts());
commands.add(new SetParam());
commands.add(new DeleteParam());
commands.add(new ExportParamContext());

View File

@ -63,7 +63,7 @@ public class DeleteParam extends AbstractUpdateParamContextCommand<VoidResult> {
// Ensure the context exists...
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingEntity = paramContextClient.getParamContext(paramContextId);
final ParameterContextEntity existingEntity = paramContextClient.getParamContext(paramContextId, false);
// Determine if this is an existing param or a new one...
final Optional<ParameterDTO> existingParam = existingEntity.getComponent().getParameters().stream()

View File

@ -53,7 +53,7 @@ public class DeleteParamContext extends AbstractNiFiCommand<StringResult> {
final String paramContextId = getRequiredArg(properties, CommandOption.PARAM_CONTEXT_ID);
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingParamContext = paramContextClient.getParamContext(paramContextId);
final ParameterContextEntity existingParamContext = paramContextClient.getParamContext(paramContextId, false);
final String version = String.valueOf(existingParamContext.getRevision().getVersion());
paramContextClient.deleteParamContext(paramContextId, version);

View File

@ -30,6 +30,7 @@ import org.apache.nifi.toolkit.cli.impl.util.JacksonUtils;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.dto.ParameterDTO;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterEntity;
import java.io.FileOutputStream;
@ -66,7 +67,7 @@ public class ExportParamContext extends AbstractNiFiCommand<ExportParamContext.E
// retrieve the context by id
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity parameterContextEntity = paramContextClient.getParamContext(paramContextId);
final ParameterContextEntity parameterContextEntity = paramContextClient.getParamContext(paramContextId, false);
// clear out values that don't make sense for importing to next environment
final ParameterContextDTO parameterContext = parameterContextEntity.getComponent();
@ -80,6 +81,16 @@ public class ExportParamContext extends AbstractNiFiCommand<ExportParamContext.E
parameterDTO.setValue(null);
}
parameterEntity.setCanWrite(null);
parameterDTO.setParameterContext(null);
}
if (parameterContext.getInheritedParameterContexts() != null) {
for (final ParameterContextReferenceEntity ref : parameterContext.getInheritedParameterContexts()) {
ref.setId(null);
ref.setPermissions(null);
ref.getComponent().setId(null);
}
}
// sort the entities so that each export is in consistent order

View File

@ -44,14 +44,16 @@ public class GetParamContext extends AbstractNiFiCommand<ParamContextResult> {
@Override
protected void doInitialize(Context context) {
addOption(CommandOption.PARAM_CONTEXT_ID.createOption());
addOption(CommandOption.PARAM_CONTEXT_INCLUDE_INHERITED.createOption());
}
@Override
public ParamContextResult doExecute(final NiFiClient client, final Properties properties)
throws NiFiClientException, IOException, MissingOptionException, CommandException {
final String paramContextId = getRequiredArg(properties, CommandOption.PARAM_CONTEXT_ID);
final boolean includeInheritedParameters = hasArg(properties, CommandOption.PARAM_CONTEXT_INCLUDE_INHERITED);
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity parameterContext = paramContextClient.getParamContext(paramContextId);
return new ParamContextResult(getResultType(properties), parameterContext);
final ParameterContextEntity parameterContext = paramContextClient.getParamContext(paramContextId, includeInheritedParameters);
return new ParamContextResult(getResultType(properties), parameterContext, includeInheritedParameters);
}
}

View File

@ -43,7 +43,8 @@ public class ImportParamContext extends AbstractNiFiCommand<StringResult> {
@Override
public String getDescription() {
return "Imports a parameter context using the output from the export-param-context command as the context to import. " +
"If the context name and context description arguments are specified, they will override what is in the context json. ";
"If the context name and context description arguments are specified, they will override what is in the context json. " +
"All inherited parameter contexts are expected to have been imported already, otherwise the operation will fail.";
}
@Override

View File

@ -46,7 +46,8 @@ public class MergeParamContext extends AbstractUpdateParamContextCommand<VoidRes
@Override
public String getDescription() {
return "Adds any parameters that exist in the exported context that don't exist in the existing context.";
return "Adds any parameters that exist in the exported context that don't exist in the existing context. Overwrites any " +
"existing inherited parameter contexts with the provided list.";
}
@Override
@ -75,7 +76,7 @@ public class MergeParamContext extends AbstractUpdateParamContextCommand<VoidRes
// retrieve the existing context by id
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingContextEntity = paramContextClient.getParamContext(existingContextId);
final ParameterContextEntity existingContextEntity = paramContextClient.getParamContext(existingContextId, false);
final ParameterContextDTO existingContext = existingContextEntity.getComponent();
if (existingContext.getParameters() == null) {
@ -110,6 +111,10 @@ public class MergeParamContext extends AbstractUpdateParamContextCommand<VoidRes
updatedContextEntity.setComponent(updatedContextDto);
updatedContextEntity.setRevision(existingContextEntity.getRevision());
if (incomingContext.getInheritedParameterContexts() != null) {
updatedContextDto.setInheritedParameterContexts(incomingContext.getInheritedParameterContexts());
}
// Submit the update request...
final ParameterContextUpdateRequestEntity updateRequestEntity = paramContextClient.updateParamContext(updatedContextEntity);
performUpdate(paramContextClient, updatedContextEntity, updateRequestEntity);

View File

@ -0,0 +1,87 @@
/*
* 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.toolkit.cli.impl.command.nifi.params;
import org.apache.commons.cli.MissingOptionException;
import org.apache.nifi.toolkit.cli.api.CommandException;
import org.apache.nifi.toolkit.cli.api.Context;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
import org.apache.nifi.toolkit.cli.impl.client.nifi.ParamContextClient;
import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterContextUpdateRequestEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class RemoveInheritedParamContexts extends AbstractUpdateParamContextCommand<VoidResult> {
public RemoveInheritedParamContexts() {
super("remove-inherited-param-contexts", VoidResult.class);
}
@Override
public String getDescription() {
return "Removes all inherited parameter contexts from the given parameter context";
}
@Override
protected void doInitialize(Context context) {
super.doInitialize(context);
addOption(CommandOption.PARAM_CONTEXT_ID.createOption());
}
@Override
public VoidResult doExecute(final NiFiClient client, final Properties properties)
throws NiFiClientException, IOException, MissingOptionException, CommandException {
// Required args...
final String paramContextId = getRequiredArg(properties, CommandOption.PARAM_CONTEXT_ID);
// Ensure the context exists...
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingParameterContextEntity = paramContextClient.getParamContext(paramContextId, false);
final ParameterContextDTO parameterContextDTO = new ParameterContextDTO();
parameterContextDTO.setId(existingParameterContextEntity.getId());
parameterContextDTO.setParameters(existingParameterContextEntity.getComponent().getParameters());
final ParameterContextEntity updatedParameterContextEntity = new ParameterContextEntity();
updatedParameterContextEntity.setId(paramContextId);
updatedParameterContextEntity.setComponent(parameterContextDTO);
updatedParameterContextEntity.setRevision(existingParameterContextEntity.getRevision());
final List<ParameterContextReferenceEntity> referenceEntities = new ArrayList<>();
parameterContextDTO.setInheritedParameterContexts(referenceEntities);
// Submit the update request...
final ParameterContextUpdateRequestEntity updateRequestEntity = paramContextClient.updateParamContext(updatedParameterContextEntity);
performUpdate(paramContextClient, updatedParameterContextEntity, updateRequestEntity);
if (isInteractive()) {
println();
}
return VoidResult.getInstance();
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.toolkit.cli.impl.command.nifi.params;
import org.apache.commons.cli.MissingOptionException;
import org.apache.nifi.toolkit.cli.api.CommandException;
import org.apache.nifi.toolkit.cli.api.Context;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
import org.apache.nifi.toolkit.cli.impl.client.nifi.ParamContextClient;
import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterContextUpdateRequestEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class SetInheritedParamContexts extends AbstractUpdateParamContextCommand<VoidResult> {
public SetInheritedParamContexts() {
super("set-inherited-param-contexts", VoidResult.class);
}
@Override
public String getDescription() {
return "Sets a list of parameter context ids from which the given parameter context should inherit parameters";
}
@Override
protected void doInitialize(Context context) {
super.doInitialize(context);
addOption(CommandOption.PARAM_CONTEXT_ID.createOption());
addOption(CommandOption.PARAM_CONTEXT_INHERITED_IDS.createOption());
}
@Override
public VoidResult doExecute(final NiFiClient client, final Properties properties)
throws NiFiClientException, IOException, MissingOptionException, CommandException {
// Required args...
final String paramContextId = getRequiredArg(properties, CommandOption.PARAM_CONTEXT_ID);
final String inheritedIds = getRequiredArg(properties, CommandOption.PARAM_CONTEXT_INHERITED_IDS);
// Ensure the context exists...
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingParameterContextEntity = paramContextClient.getParamContext(paramContextId, false);
final String[] inheritedIdArray = inheritedIds.split(",");
final List<ParameterContextReferenceEntity> referenceEntities = new ArrayList<>();
for(final String inheritedId : inheritedIdArray) {
final ParameterContextEntity existingInheritedEntity = paramContextClient.getParamContext(inheritedId, false);
final ParameterContextReferenceEntity parameterContextReferenceEntity = new ParameterContextReferenceEntity();
parameterContextReferenceEntity.setId(existingInheritedEntity.getId());
final ParameterContextReferenceDTO parameterContextReferenceDTO = new ParameterContextReferenceDTO();
parameterContextReferenceDTO.setName(existingInheritedEntity.getComponent().getName());
parameterContextReferenceDTO.setId(existingInheritedEntity.getComponent().getId());
parameterContextReferenceEntity.setComponent(parameterContextReferenceDTO);
referenceEntities.add(parameterContextReferenceEntity);
}
final ParameterContextDTO parameterContextDTO = new ParameterContextDTO();
parameterContextDTO.setId(existingParameterContextEntity.getId());
parameterContextDTO.setParameters(existingParameterContextEntity.getComponent().getParameters());
final ParameterContextEntity updatedParameterContextEntity = new ParameterContextEntity();
updatedParameterContextEntity.setId(paramContextId);
updatedParameterContextEntity.setComponent(parameterContextDTO);
updatedParameterContextEntity.setRevision(existingParameterContextEntity.getRevision());
parameterContextDTO.setInheritedParameterContexts(referenceEntities);
// Submit the update request...
final ParameterContextUpdateRequestEntity updateRequestEntity = paramContextClient.updateParamContext(updatedParameterContextEntity);
performUpdate(paramContextClient, updatedParameterContextEntity, updateRequestEntity);
if (isInteractive()) {
println();
}
return VoidResult.getInstance();
}
}

View File

@ -75,7 +75,7 @@ public class SetParam extends AbstractUpdateParamContextCommand<VoidResult> {
// Ensure the context exists...
final ParamContextClient paramContextClient = client.getParamContextClient();
final ParameterContextEntity existingParameterContextEntity = paramContextClient.getParamContext(paramContextId);
final ParameterContextEntity existingParameterContextEntity = paramContextClient.getParamContext(paramContextId, false);
final ParameterContextDTO existingParameterContextDTO = existingParameterContextEntity.getComponent();
// Determine if this is an existing param or a new one...

View File

@ -37,10 +37,13 @@ import java.util.stream.Collectors;
public class ParamContextResult extends AbstractWritableResult<ParameterContextEntity> {
private final ParameterContextEntity parameterContext;
private final boolean includeParameterContextSource;
public ParamContextResult(final ResultType resultType, final ParameterContextEntity parameterContext) {
public ParamContextResult(final ResultType resultType, final ParameterContextEntity parameterContext,
final boolean includeParameterContextSource) {
super(resultType);
this.parameterContext = parameterContext;
this.includeParameterContextSource = includeParameterContextSource;
}
@Override
@ -54,21 +57,34 @@ public class ParamContextResult extends AbstractWritableResult<ParameterContextE
.map(p -> p.getParameter())
.collect(Collectors.toSet());
final List<ParameterDTO> sortedParams =paramDTOs.stream()
.sorted(Comparator.comparing(ParameterDTO::getName))
final List<ParameterDTO> sortedParams = paramDTOs.stream()
.sorted(Comparator
.comparing((ParameterDTO param) -> // Direct parameters first
param.getParameterContext().getComponent().getId().equals((this.parameterContext.getComponent().getId())))
.reversed()
.thenComparing(ParameterDTO::getName))
.collect(Collectors.toList());
final Table table = new Table.Builder()
final Table.Builder tableBuilder = new Table.Builder()
.column("#", 3, 3, false)
.column("Name", 20, 60, false)
.column("Value", 20, 80, false)
.column("Sensitive", 10, 10, false)
.column("Description", 20, 80, true)
.column("Sensitive", 10, 10, false);
if (includeParameterContextSource) {
tableBuilder.column("Source Parameter Context", 25, 40, true);
}
final Table table = tableBuilder.column("Description", 20, 80, true)
.build();
for (int i = 0; i < sortedParams.size(); i++) {
final ParameterDTO r = sortedParams.get(i);
table.addRow(String.valueOf(i+1), r.getName(), r.getValue(), r.getSensitive().toString(), r.getDescription());
final String[] row = includeParameterContextSource
? new String[] { String.valueOf(i+1), r.getName(), r.getValue(), r.getSensitive().toString(),
r.getParameterContext().getComponent().getName(), r.getDescription() }
: new String[] { String.valueOf(i+1), r.getName(), r.getValue(), r.getSensitive().toString(),
r.getDescription() };
table.addRow(row);
}
final TableWriter tableWriter = new DynamicTableWriter();

View File

@ -40,6 +40,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class ParamContextsResult extends AbstractWritableResult<ParameterContextsEntity> implements Referenceable {
@ -75,12 +76,15 @@ public class ParamContextsResult extends AbstractWritableResult<ParameterContext
.column("#", 3, 3, false)
.column("Id", 36, 36, false)
.column("Name", 20, 60, true)
.column("Inherited Param Contexts", 20, 60, true)
.column("Description", 40, 60, true)
.build();
for (int i = 0; i < results.size(); i++) {
final ParameterContextDTO r = results.get(i);
table.addRow("" + (i+1), r.getId(), r.getName(), r.getDescription());
final String inheritedParamContexts = r.getInheritedParameterContexts() == null ? ""
: r.getInheritedParameterContexts().stream().map(pc -> pc.getComponent().getName()).collect(Collectors.joining(", "));
table.addRow("" + (i+1), r.getId(), r.getName(), inheritedParamContexts, r.getDescription());
}
final TableWriter tableWriter = new DynamicTableWriter();