Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/main/java/org/apache/nifi/components/DescribedValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,24 @@ public interface DescribedValue {
String getDisplayName();

/**
* @return the proeprty description as a string
* @return the property description as a string
*/
String getDescription();

DescribedValue NULL = new DescribedValue() {
@Override
public String getValue() {
return null;
}

@Override
public String getDisplayName() {
return "NULL";
}

@Override
public String getDescription() {
return "A null value";
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ protected void ensureDrainageUnblocked() throws InvocationFailedException {
public List<ConfigVerificationResult> verify(final FlowContext flowContext) {
final List<ConfigVerificationResult> results = new ArrayList<>();

final List<ConfigurationStep> configSteps = getConfigurationSteps(flowContext);
final List<ConfigurationStep> configSteps = getConfigurationSteps();
for (final ConfigurationStep configStep : configSteps) {
final List<ConfigVerificationResult> stepResults = verifyConfigurationStep(configStep.getName(), Map.of(), flowContext);
results.addAll(stepResults);
Expand All @@ -286,9 +286,14 @@ public List<ConfigVerificationResult> verify(final FlowContext flowContext) {
public List<ValidationResult> validate(final FlowContext flowContext, final ConnectorValidationContext validationContext) {
final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
final List<ValidationResult> results = new ArrayList<>();
final List<ConfigurationStep> configurationSteps = getConfigurationSteps(flowContext);
final List<ConfigurationStep> configurationSteps = getConfigurationSteps();

for (final ConfigurationStep configurationStep : configurationSteps) {
if (!isStepDependencySatisfied(configurationStep, configurationSteps, configContext)) {
getLogger().debug("Skipping validation for Configuration Step [{}] because its dependencies are not satisfied", configurationStep.getName());
continue;
}

results.addAll(validateConfigurationStep(configurationStep, configContext, validationContext));
}

Expand Down Expand Up @@ -545,6 +550,72 @@ private boolean isDependencySatisfied(final ConnectorPropertyDescriptor property
}
}

private boolean isStepDependencySatisfied(final ConfigurationStep step, final List<ConfigurationStep> allSteps,
final ConnectorConfigurationContext configContext) {

final Set<ConfigurationStepDependency> dependencies = step.getDependencies();
if (dependencies.isEmpty()) {
return true;
}

for (final ConfigurationStepDependency dependency : dependencies) {
final String dependentStepName = dependency.getStepName();
final String dependentPropertyName = dependency.getPropertyName();

final ConfigurationStep dependentStep = findStepByName(allSteps, dependentStepName);
if (dependentStep == null) {
getLogger().debug("Dependency of step {} is not satisfied because it depends on step {} which could not be found", step.getName(), dependentStepName);
return false;
}

final ConnectorPropertyDescriptor dependentProperty = findPropertyInStep(dependentStep, dependentPropertyName);
if (dependentProperty == null) {
getLogger().debug("Dependency of step {} is not satisfied because it depends on property {} in step {} which could not be found",
step.getName(), dependentPropertyName, dependentStepName);
return false;
}

final ConnectorPropertyValue propertyValue = configContext.getProperty(dependentStepName, dependentPropertyName);
final String value = propertyValue == null ? dependentProperty.getDefaultValue() : propertyValue.getValue();

final Set<String> dependentValues = dependency.getDependentValues();
if (dependentValues == null) {
// Dependency is satisfied as long as the property has any value configured.
if (value == null) {
return false;
}

continue;
}

if (!dependentValues.contains(value)) {
return false;
}
}

return true;
}

private ConfigurationStep findStepByName(final List<ConfigurationStep> steps, final String stepName) {
for (final ConfigurationStep step : steps) {
if (step.getName().equals(stepName)) {
return step;
}
}
return null;
}

private ConnectorPropertyDescriptor findPropertyInStep(final ConfigurationStep step, final String propertyName) {
for (final ConnectorPropertyGroup group : step.getPropertyGroups()) {
for (final ConnectorPropertyDescriptor descriptor : group.getProperties()) {
if (descriptor.getName().equals(propertyName)) {
return descriptor;
}
}
}
return null;
}

@Override
public final void onConfigurationStepConfigured(final String stepName, final FlowContext workingContext) throws FlowUpdateException {
onStepConfigured(stepName, workingContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,28 @@

package org.apache.nifi.components.connector;

import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.DescribedValue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public final class ConfigurationStep {
private final String name;
private final String description;
private final List<ConnectorPropertyGroup> propertyGroups;
private final Set<ConfigurationStepDependency> dependencies;

private ConfigurationStep(final Builder builder) {
this.name = builder.name;
this.description = builder.description;
this.propertyGroups = Collections.unmodifiableList(builder.propertyGroups);
this.dependencies = Collections.unmodifiableSet(builder.dependencies);
}

public String getName() {
Expand All @@ -46,17 +53,25 @@ public List<ConnectorPropertyGroup> getPropertyGroups() {
return propertyGroups;
}

/**
* @return the set of dependencies that this step has on other steps' properties
*/
public Set<ConfigurationStepDependency> getDependencies() {
return dependencies;
}

public static final class Builder {
private String name;
private String description;
private List<ConnectorPropertyGroup> propertyGroups = Collections.emptyList();
private final Set<ConfigurationStepDependency> dependencies = new HashSet<>();

public Builder name(String name) {
public Builder name(final String name) {
this.name = name;
return this;
}

public Builder description(String description) {
public Builder description(final String description) {
this.description = description;
return this;
}
Expand All @@ -66,6 +81,75 @@ public Builder propertyGroups(final List<ConnectorPropertyGroup> propertyGroups)
return this;
}

/**
* Sets a dependency on another ConfigurationStep's property having some (any) value configured.
*
* @param step the ConfigurationStep that this step depends on
* @param property the property within the specified step that must have a value
* @return this Builder for method chaining
*/
public Builder dependsOn(final ConfigurationStep step, final ConnectorPropertyDescriptor property) {
dependencies.add(new ConfigurationStepDependency(step.getName(), property.getName()));
return this;
}

/**
* Sets a dependency on another ConfigurationStep's property having one of the specified values.
*
* @param step the ConfigurationStep that this step depends on
* @param property the property within the specified step that must have one of the specified values
* @param dependentValues the list of values that satisfy this dependency
* @return this Builder for method chaining
*/
public Builder dependsOn(final ConfigurationStep step, final ConnectorPropertyDescriptor property, final List<DescribedValue> dependentValues) {
if (dependentValues == null || dependentValues.isEmpty()) {
dependencies.add(new ConfigurationStepDependency(step.getName(), property.getName()));
} else {
final Set<String> dependentValueSet = dependentValues.stream()
.map(DescribedValue::getValue)
.collect(Collectors.toSet());

dependencies.add(new ConfigurationStepDependency(step.getName(), property.getName(), dependentValueSet));
}

return this;
}

/**
* Sets a dependency on another ConfigurationStep's property having one of the specified values.
*
* @param step the ConfigurationStep that this step depends on
* @param property the property within the specified step that must have one of the specified values
* @param firstDependentValue the first value that satisfies this dependency
* @param additionalDependentValues additional values that satisfy this dependency
* @return this Builder for method chaining
*/
public Builder dependsOn(final ConfigurationStep step, final ConnectorPropertyDescriptor property,
final DescribedValue firstDependentValue, final DescribedValue... additionalDependentValues) {

final List<DescribedValue> dependentValues = new ArrayList<>();
dependentValues.add(firstDependentValue);
dependentValues.addAll(Arrays.asList(additionalDependentValues));
return dependsOn(step, property, dependentValues);
}

/**
* Sets a dependency on another ConfigurationStep's property having one of the specified string values.
*
* @param step the ConfigurationStep that this step depends on
* @param property the property within the specified step that must have one of the specified values
* @param dependentValues the string values that satisfy this dependency
* @return this Builder for method chaining
*/
public Builder dependsOn(final ConfigurationStep step, final ConnectorPropertyDescriptor property, final String... dependentValues) {
final List<DescribedValue> describedValues = Arrays.stream(dependentValues)
.map(AllowableValue::new)
.map(DescribedValue.class::cast)
.toList();

return dependsOn(step, property, describedValues);
}

public ConfigurationStep build() {
if (name == null) {
throw new IllegalStateException("Configuration Step's name must be provided");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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.components.connector;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
* Represents a dependency that a ConfigurationStep has on another ConfigurationStep's property.
* A ConfigurationStep can depend on a property of another step having some value (when dependentValues is null
* and requiresAbsence is false), having one of a specific set of values (when dependentValues is non-null),
* or requiring the property to be absent/null (when requiresAbsence is true).
*/
public final class ConfigurationStepDependency {
private final String stepName;
private final String propertyName;
private final Set<String> dependentValues;

/**
* Creates a dependency on the specified step and property having one of the specified values.
*
* @param stepName the name of the step that this dependency is on
* @param propertyName the name of the property that this dependency is on
* @param dependentValues the set of values that the property must have; if the property has any
* of these values, the dependency is satisfied
*/
public ConfigurationStepDependency(final String stepName, final String propertyName, final Set<String> dependentValues) {
this.stepName = Objects.requireNonNull(stepName, "Step name is required");
this.propertyName = Objects.requireNonNull(propertyName, "Property name is required");
this.dependentValues = dependentValues == null ? null : new HashSet<>(dependentValues);
}

/**
* Creates a dependency on the specified step and property having some (any) value.
*
* @param stepName the name of the step that this dependency is on
* @param propertyName the name of the property that this dependency is on
*/
public ConfigurationStepDependency(final String stepName, final String propertyName) {
this.stepName = Objects.requireNonNull(stepName, "Step name is required");
this.propertyName = Objects.requireNonNull(propertyName, "Property name is required");
this.dependentValues = null;
}

/**
* @return the name of the ConfigurationStep that this dependency references
*/
public String getStepName() {
return stepName;
}

/**
* @return the name of the property within the referenced step that this dependency is on
*/
public String getPropertyName() {
return propertyName;
}

/**
* @return the set of values that the referenced property must have for the dependency to be satisfied,
* or null if the dependency is satisfied by the property having any value (or being absent if requiresAbsence is true)
*/
public Set<String> getDependentValues() {
return dependentValues;
}


@Override
public boolean equals(final Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
final ConfigurationStepDependency that = (ConfigurationStepDependency) o;
return Objects.equals(stepName, that.stepName)
&& Objects.equals(propertyName, that.propertyName)
&& Objects.equals(dependentValues, that.dependentValues);
}

@Override
public int hashCode() {
return Objects.hash(stepName, propertyName, dependentValues);
}

@Override
public String toString() {
return "ConfigurationStepDependency[stepName=" + stepName + ", propertyName=" + propertyName
+ ", dependentValues=" + dependentValues + "]";
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ public interface Connector {
* represents a logical grouping of properties that should be configured together. The order of the steps
* in the list represents the order in which the steps should be configured.
*
* @param flowContext the flow context that houses the configuration being used to drive the available configuration steps
* @return the list of configuration steps
*/
List<ConfigurationStep> getConfigurationSteps(FlowContext flowContext);
List<ConfigurationStep> getConfigurationSteps();

/**
* Called whenever a specific configuration step has been configured. This allows the Connector to perform any necessary
Expand Down
Loading