diff --git a/nifi-api/src/main/java/org/apache/nifi/components/AllowableValue.java b/nifi-api/src/main/java/org/apache/nifi/components/AllowableValue.java index b8303ae122..3a76683274 100644 --- a/nifi-api/src/main/java/org/apache/nifi/components/AllowableValue.java +++ b/nifi-api/src/main/java/org/apache/nifi/components/AllowableValue.java @@ -43,9 +43,8 @@ public class AllowableValue implements DescribedValue { * Constructs a new AllowableValue with the given value and display name and * no description * - * @param value that is allowed + * @param value that is allowed * @param displayName to display for the value - * * @throws NullPointerException if either argument is null */ public AllowableValue(final String value, final String displayName) { @@ -56,10 +55,9 @@ public class AllowableValue implements DescribedValue { * Constructs a new AllowableValue with the given value, display name, and * description * - * @param value that is valid + * @param value that is valid * @param displayName to show for the value * @param description of the value - * * @throws NullPointerException if identifier or value is null */ public AllowableValue(final String value, final String displayName, final String description) { @@ -68,6 +66,14 @@ public class AllowableValue implements DescribedValue { this.description = description; } + public static AllowableValue fromDescribedValue(final DescribedValue describedValue) { + if (describedValue instanceof AllowableValue allowableValue) { + return allowableValue; + } + + return new AllowableValue(describedValue.getValue(), describedValue.getDisplayName(), describedValue.getDescription()); + } + /** * @return the value of this AllowableValue */ @@ -106,8 +112,7 @@ public class AllowableValue implements DescribedValue { return true; } - if (obj instanceof AllowableValue) { - final AllowableValue other = (AllowableValue) obj; + if (obj instanceof AllowableValue other) { return (this.value.equals(other.getValue())); } else if (obj instanceof String) { return this.value.equals(obj); diff --git a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java index 1d039058f4..35d0a8f131 100644 --- a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java +++ b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java @@ -19,9 +19,9 @@ package org.apache.nifi.components; import org.apache.nifi.components.resource.ResourceCardinality; import org.apache.nifi.components.resource.ResourceDefinition; import org.apache.nifi.components.resource.ResourceReference; -import org.apache.nifi.components.resource.StandardResourceReferenceFactory; import org.apache.nifi.components.resource.ResourceType; import org.apache.nifi.components.resource.StandardResourceDefinition; +import org.apache.nifi.components.resource.StandardResourceReferenceFactory; import org.apache.nifi.controller.ControllerService; import org.apache.nifi.expression.ExpressionLanguageScope; @@ -38,7 +38,6 @@ import java.util.Set; /** * An immutable object for holding information about a type of component * property. - * */ public final class PropertyDescriptor implements Comparable { @@ -123,15 +122,15 @@ public final class PropertyDescriptor implements Comparable this.name = builder.name; this.description = builder.description; this.defaultValue = builder.defaultValue; - this.allowableValues = builder.allowableValues == null ? null : Collections.unmodifiableList(new ArrayList<>(builder.allowableValues)); + this.allowableValues = builder.allowableValues == null ? null : List.copyOf(builder.allowableValues); this.required = builder.required; this.sensitive = builder.sensitive; this.dynamic = builder.dynamic; this.dynamicallyModifiesClasspath = builder.dynamicallyModifiesClasspath; this.expressionLanguageScope = builder.expressionLanguageScope; this.controllerServiceDefinition = builder.controllerServiceDefinition; - this.validators = Collections.unmodifiableList(new ArrayList<>(builder.validators)); - this.dependencies = builder.dependencies == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(builder.dependencies)); + this.validators = List.copyOf(builder.validators); + this.dependencies = builder.dependencies == null ? Collections.emptySet() : Set.copyOf(builder.dependencies); this.resourceDefinition = builder.resourceDefinition; } @@ -148,7 +147,7 @@ public final class PropertyDescriptor implements Comparable * If this descriptor has a set of allowable values then the given value is * only checked against the allowable values. * - * @param input the value to validate + * @param input the value to validate * @param context the context of validation * @return the result of validating the input */ @@ -188,15 +187,15 @@ public final class PropertyDescriptor implements Comparable final ControllerService service = context.getControllerServiceLookup().getControllerService(input); if (service == null) { return new ValidationResult.Builder() - .input(input) - .subject(getDisplayName()) - .valid(false) - .explanation("Property references a Controller Service that does not exist") - .build(); + .input(input) + .subject(getDisplayName()) + .valid(false) + .explanation("Property references a Controller Service that does not exist") + .build(); } else { return new ValidationResult.Builder() - .valid(true) - .build(); + .valid(true) + .build(); } } @@ -293,15 +292,13 @@ public final class PropertyDescriptor implements Comparable } /** - * Specifies the initial value and the default value that will be used - * if the user does not specify a value. When {@link #build()} is - * called, if Allowable Values have been set (see - * {@link #allowableValues(AllowableValue...)}) and this value is not - * one of those Allowable Values, an Exception will be thrown. If the - * Allowable Values have been set using the - * {@link #allowableValues(AllowableValue...)} method, the default value - * should be set to the "Value" of the {@link AllowableValue} object - * (see {@link AllowableValue#getValue()}). + * Specifies the initial value and the default value that will be used if the user does not specify a value. + *

+ * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) + * and this value is not one of those Allowable Values, an Exception will be thrown. + * If the Allowable Values have been set, the default value should be set to + * the "Value" of the {@link DescribedValue} object (see {@link DescribedValue#getValue()}). + * There's an overload available for this (see {@link #defaultValue(DescribedValue)}). * * @param value default value * @return the builder @@ -314,15 +311,12 @@ public final class PropertyDescriptor implements Comparable } /** - * Specifies the initial value and the default value that will be used - * if the user does not specify a value. When {@link #build()} is - * called, if Allowable Values have been set (see - * {@link #allowableValues(AllowableValue...)}) - * and the "Value" of the {@link DescribedValue} object is not - * the "Value" of one of those Allowable Values, an Exception will be thrown. - * If the Allowable Values have been set using the - * {@link #allowableValues(AllowableValue...)} method, the default value - * should be set providing the {@link AllowableValue} to this method. + * Specifies the initial value and the default value that will be used if the user does not specify a value. + *

+ * Sets the default value to the "Value" of the {@link DescribedValue} object. + * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) + * and this value is not one of those Allowable Values, an Exception will be thrown. + * In case there is not a restricted set of Allowable Values {@link #defaultValue(String)} may be used. * * @param value default value holder * @return the builder @@ -331,6 +325,16 @@ public final class PropertyDescriptor implements Comparable return defaultValue(value != null ? value.getValue() : null); } + /** + * Clears the initial value and default value from this Property. + * + * @return the builder + */ + public Builder clearDefaultValue() { + this.defaultValue = null; + return this; + } + public Builder dynamic(final boolean dynamic) { this.dynamic = dynamic; return this; @@ -342,17 +346,17 @@ public final class PropertyDescriptor implements Comparable * libraries for the given component. *

* NOTE: If a component contains a PropertyDescriptor where dynamicallyModifiesClasspath is set to true, - * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will - * be loaded by a separate InstanceClassLoader for each processor instance.
- * It also allows to load native libraries from the extra classpath. - *

- * One can chose to omit the annotation. In this case the loading of native libraries from the extra classpath - * is not supported. - * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an - * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually - * to load required classes on an instance-by-instance basis - * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example). - * + * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will + * be loaded by a separate InstanceClassLoader for each processor instance.
+ * It also allows to load native libraries from the extra classpath. + *

+ * One can choose to omit the annotation. In this case the loading of native libraries from the extra classpath + * is not supported. + * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an + * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually + * to load required classes on an instance-by-instance basis + * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example). + *

* Any property descriptor that dynamically modifies the classpath should also make use of the {@link #identifiesExternalResource(ResourceCardinality, ResourceType, ResourceType...)} method * to indicate that the property descriptor references external resources and optionally restrict which types of resources and how many resources the property allows. * @@ -365,68 +369,99 @@ public final class PropertyDescriptor implements Comparable } /** - * @param values contrained set of values + * Sets the Allowable Values for this Property. + * + * @param values constrained set of values * @return the builder */ public Builder allowableValues(final Set values) { if (null != values) { - this.allowableValues = new ArrayList<>(); - - for (final String value : values) { - this.allowableValues.add(new AllowableValue(value, value)); - } + this.allowableValues = values.stream().map(AllowableValue::new).toList(); } return this; } + /** + * Sets the Allowable Values for this Property. + *

+ * Uses the {@link Enum#name()} of each value as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param values constrained set of values + * @return the builder + */ public > Builder allowableValues(final E[] values) { if (null != values) { - this.allowableValues = new ArrayList<>(); - for (final E value : values) { - allowableValues.add(new AllowableValue(value.name(), value.name())); - } + this.allowableValues = Arrays.stream(values) + .map(enumValue -> enumValue instanceof DescribedValue describedValue + ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) + .toList(); } return this; } /** - * Stores allowable values from an enum class. - * @param enumClass an enum class that implements the DescribedValue interface and contains a set of values - * @param generic parameter for an enum class that implements the DescribedValue interface + * Sets the Allowable Values for this Property. + *

+ * Uses the {@link Enum#name()} of each value from {@link Class#getEnumConstants()} as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param enumClass an enum class that contains a set of values and optionally implements the DescribedValue interface + * @param generic parameter for an enum class, that may implement the DescribedValue interface * @return the builder */ - public & DescribedValue> Builder allowableValues(final Class enumClass) { - this.allowableValues = new ArrayList<>(); - for (E enumValue : enumClass.getEnumConstants()) { - this.allowableValues.add(new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription())); - } - return this; + public > Builder allowableValues(final Class enumClass) { + return allowableValues(enumClass.getEnumConstants()); } /** - * Stores allowable values from a set of enum values. - * @param enumValues a set of enum values that implements the DescribedValue interface - * @param generic parameter for the enum values' class that implements the DescribedValue interface + * Sets the Allowable Values for this Property. + *

+ * Uses the {@link Enum#name()} of each value of the {@link EnumSet} as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param enumValues an enum set that contains a set of values and optionally implements the DescribedValue interface + * @param generic parameter for an enum class, that may implement the DescribedValue interface * @return the builder */ - public & DescribedValue> Builder allowableValues(final EnumSet enumValues) { - this.allowableValues = new ArrayList<>(); - for (E enumValue : enumValues) { - this.allowableValues.add(new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription())); + public > Builder allowableValues(final EnumSet enumValues) { + if (null != enumValues) { + this.allowableValues = enumValues.stream() + .map(enumValue -> enumValue instanceof DescribedValue describedValue + ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) + .toList(); } return this; } /** + * Sets the Allowable Values for this Property. + * * @param values constrained set of values * @return the builder */ public Builder allowableValues(final String... values) { if (null != values) { - this.allowableValues = new ArrayList<>(); - for (final String value : values) { - allowableValues.add(new AllowableValue(value, value)); - } + this.allowableValues = Arrays.stream(values).map(AllowableValue::new).toList(); + } + return this; + } + + /** + * Sets the Allowable Values for this Property. + *

+ * Uses the information provided by each {@link DescribedValue} (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, + * {@link DescribedValue#getDescription()}) to populate the {@link AllowableValue}s. + * + * @param values constrained set of values + * @return the builder + */ + public Builder allowableValues(final DescribedValue... values) { + if (null != values) { + this.allowableValues = Arrays.stream(values).map(AllowableValue::fromDescribedValue).toList(); } return this; } @@ -441,19 +476,6 @@ public final class PropertyDescriptor implements Comparable return this; } - /** - * Sets the Allowable Values for this Property - * - * @param values contrained set of values - * @return the builder - */ - public Builder allowableValues(final AllowableValue... values) { - if (null != values) { - this.allowableValues = Arrays.asList(values); - } - return this; - } - /** * @param required true if yes; false otherwise * @return the builder @@ -498,7 +520,7 @@ public final class PropertyDescriptor implements Comparable * Service that implements the given interface * * @param controllerServiceDefinition the interface that is implemented - * by the Controller Service + * by the Controller Service * @return the builder */ public Builder identifiesControllerService(final Class controllerServiceDefinition) { @@ -533,12 +555,12 @@ public final class PropertyDescriptor implements Comparable * *

  • If the ResourceCardinality is MULTIPLE, the given property value may consist of one or more resources, each separted by a comma and optional white space.
  • * - * + *

    * Generally, any property descriptor that makes use of the {@link #dynamicallyModifiesClasspath(boolean)} method to dynamically update its classpath should also * make use of this method, specifying which types of resources are allowed and how many. * - * @param cardinality specifies how many resources the property should allow - * @param resourceType the type of resource that is allowed + * @param cardinality specifies how many resources the property should allow + * @param resourceType the type of resource that is allowed * @param additionalResourceTypes if more than one type of resource is allowed, any resource type in addition to the given resource type may be provided * @return the builder */ @@ -558,15 +580,15 @@ public final class PropertyDescriptor implements Comparable * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a non-null value. * Furthermore, if one or more explicit Allowable Values are provided, this Property will not be relevant unless the given Property's value is equal to one of the given Allowable Values. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. - * + *

    * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. - * + *

    * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * - * @param property the property that must be set in order for this property to become relevant + * @param property the property that must be set in order for this property to become relevant * @param dependentValues the possible values for the given property for which this Property is relevant * @return the builder */ @@ -593,16 +615,16 @@ public final class PropertyDescriptor implements Comparable * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given * String arguments. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. - * + *

    * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. - * + *

    * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * - * @param property the property that must be set in order for this property to become relevant - * @param firstDependentValue the first value for the given property for which this Property is relevant + * @param property the property that must be set in order for this property to become relevant + * @param firstDependentValue the first value for the given property for which this Property is relevant * @param additionalDependentValues any other values for the given property for which this Property is relevant * @return the builder */ @@ -621,25 +643,25 @@ public final class PropertyDescriptor implements Comparable * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given * {@link DescribedValue} arguments. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. - * + *

    * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Described Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. - * + *

    * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * - * @param property the property that must be set in order for this property to become relevant - * @param firstDependentValue the first value for the given property for which this Property is relevant + * @param property the property that must be set in order for this property to become relevant + * @param firstDependentValue the first value for the given property for which this Property is relevant * @param additionalDependentValues any other values for the given property for which this Property is relevant * @return the builder */ public Builder dependsOn(final PropertyDescriptor property, final DescribedValue firstDependentValue, final DescribedValue... additionalDependentValues) { final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1]; - dependentValues[0] = toAllowableValue(firstDependentValue); + dependentValues[0] = AllowableValue.fromDescribedValue(firstDependentValue); int i = 1; for (final DescribedValue additionalDependentValue : additionalDependentValues) { - dependentValues[i++] = toAllowableValue(additionalDependentValue); + dependentValues[i++] = AllowableValue.fromDescribedValue(additionalDependentValue); } return dependsOn(property, dependentValues); @@ -655,16 +677,11 @@ public final class PropertyDescriptor implements Comparable return this; } - private AllowableValue toAllowableValue(DescribedValue describedValue) { - return new AllowableValue(describedValue.getValue(), describedValue.getDisplayName(), describedValue.getDescription()); - } - /** * @return a PropertyDescriptor as configured - * * @throws IllegalStateException if allowable values are configured but - * no default value is set, or the default value is not contained within - * the allowable values. + * no default value is set, or the default value is not contained within + * the allowable values. */ public PropertyDescriptor build() { if (name == null) { @@ -740,18 +757,14 @@ public final class PropertyDescriptor implements Comparable @Override public boolean equals(final Object other) { - if (other == null) { - return false; - } - if (!(other instanceof PropertyDescriptor)) { - return false; - } if (this == other) { return true; } + if (other instanceof PropertyDescriptor otherPropertyDescriptor) { + return this.name.equals(otherPropertyDescriptor.name); + } - final PropertyDescriptor desc = (PropertyDescriptor) other; - return this.name.equals(desc.name); + return false; } @Override @@ -768,7 +781,7 @@ public final class PropertyDescriptor implements Comparable private static final String POSITIVE_EXPLANATION = "Given value found in allowed set"; private static final String NEGATIVE_EXPLANATION = "Given value not found in allowed set '%1$s'"; - private static final String VALUE_DEMARCATOR = ", "; + private static final String VALUE_DELIMITER = ", "; private final String validStrings; private final Collection validValues; @@ -780,20 +793,8 @@ public final class PropertyDescriptor implements Comparable * @throws NullPointerException if the given validValues is null */ private ConstrainedSetValidator(final Collection validValues) { - String validVals = ""; - if (!validValues.isEmpty()) { - final StringBuilder valuesBuilder = new StringBuilder(); - for (final AllowableValue value : validValues) { - valuesBuilder.append(value).append(VALUE_DEMARCATOR); - } - validVals = valuesBuilder.substring(0, valuesBuilder.length() - VALUE_DEMARCATOR.length()); - } - validStrings = validVals; - - this.validValues = new ArrayList<>(validValues.size()); - for (final AllowableValue value : validValues) { - this.validValues.add(value.getValue()); - } + this.validValues = validValues.stream().map(AllowableValue::getValue).toList(); + this.validStrings = String.join(VALUE_DELIMITER, this.validValues); } @Override @@ -824,13 +825,13 @@ public final class PropertyDescriptor implements Comparable @Override public ValidationResult validate(final String subject, final String configuredInput, final ValidationContext context) { final ValidationResult.Builder resultBuilder = new ValidationResult.Builder() - .input(configuredInput) - .subject(subject); + .input(configuredInput) + .subject(subject); if (configuredInput == null) { return resultBuilder.valid(false) - .explanation("No value specified") - .build(); + .explanation("No value specified") + .build(); } // If Expression Language is supported and is used in the property value, we cannot perform validation against the configured @@ -843,8 +844,8 @@ public final class PropertyDescriptor implements Comparable resultBuilder.input(input); } else { return resultBuilder.valid(true) - .explanation("Expression Language is present, so validation of property value cannot be performed") - .build(); + .explanation("Expression Language is present, so validation of property value cannot be performed") + .build(); } } @@ -854,15 +855,15 @@ public final class PropertyDescriptor implements Comparable final boolean allowsText = resourceDefinition.getResourceTypes().contains(ResourceType.TEXT); if (allowsText) { return resultBuilder.valid(true) - .explanation("Property allows for Resource Type of Text, so validation of property value cannot be performed") - .build(); + .explanation("Property allows for Resource Type of Text, so validation of property value cannot be performed") + .build(); } final String[] splits = input.split(","); if (resourceDefinition.getCardinality() == ResourceCardinality.SINGLE && splits.length > 1) { return resultBuilder.valid(false) - .explanation("Property only supports a single Resource but " + splits.length + " resources were specified") - .build(); + .explanation("Property only supports a single Resource but " + splits.length + " resources were specified") + .build(); } final Set resourceTypes = resourceDefinition.getResourceTypes(); @@ -885,25 +886,25 @@ public final class PropertyDescriptor implements Comparable if (!resourceTypes.contains(resourceReference.getResourceType())) { return resultBuilder.valid(false) - .explanation("Specified Resource is a " + resourceReference.getResourceType().name() + " but this property does not allow this type of resource") - .build(); + .explanation("Specified Resource is a " + resourceReference.getResourceType().name() + " but this property does not allow this type of resource") + .build(); } } if (count == 0) { return resultBuilder.valid(false) - .explanation("No resources were specified") - .build(); + .explanation("No resources were specified") + .build(); } if (!nonExistentResources.isEmpty()) { return resultBuilder.valid(false) - .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentResources) - .build(); + .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentResources) + .build(); } return resultBuilder.valid(true) - .build(); + .build(); } } } diff --git a/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java b/nifi-api/src/test/java/org/apache/nifi/components/EnumDescribedValue.java similarity index 93% rename from nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java rename to nifi-api/src/test/java/org/apache/nifi/components/EnumDescribedValue.java index e09b9e8278..1e4fef8c5f 100644 --- a/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java +++ b/nifi-api/src/test/java/org/apache/nifi/components/EnumDescribedValue.java @@ -16,7 +16,7 @@ */ package org.apache.nifi.components; -public enum EnumAllowableValue implements DescribedValue { +public enum EnumDescribedValue implements DescribedValue { GREEN { @Override diff --git a/nifi-api/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java b/nifi-api/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java new file mode 100644 index 0000000000..a280c9c70c --- /dev/null +++ b/nifi-api/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java @@ -0,0 +1,21 @@ +/* + * 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; + +public enum EnumNotDescribedValue { + GREEN, RED, BLUE; +} diff --git a/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java b/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java index fcaa0f7a1f..c3288e8f5f 100644 --- a/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java +++ b/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java @@ -20,17 +20,16 @@ import org.apache.nifi.components.PropertyDescriptor.Builder; import org.apache.nifi.components.resource.ResourceCardinality; import org.apache.nifi.components.resource.ResourceType; import org.apache.nifi.expression.ExpressionLanguageScope; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Arrays; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -40,77 +39,180 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; -/** - * Regression test for issue NIFI-49, to ensure that if a Processor's Property's - * Default Value is not allowed, the Exception thrown should indicate what the - * default value is - */ public class TestPropertyDescriptor { - private static Builder invalidDescriptorBuilder; - private static Builder validDescriptorBuilder; private static final String DEFAULT_VALUE = "Default Value"; private static final String DEPENDENT_PROPERTY_NAME = "dependentProperty"; - @BeforeAll - public static void setUp() { - validDescriptorBuilder = new PropertyDescriptor.Builder().name("").allowableValues("Allowable Value", "Another Allowable Value").defaultValue("Allowable Value"); - invalidDescriptorBuilder = new PropertyDescriptor.Builder().name("").allowableValues("Allowable Value", "Another Allowable Value").defaultValue(DEFAULT_VALUE); + @Nested + class RegardingDefaultValue { + @Test + void supportsStringValues() { + final PropertyDescriptor descriptor = builder().defaultValue(DEFAULT_VALUE).build(); + + assertEquals(DEFAULT_VALUE, descriptor.getDefaultValue()); + } + + @Test + void supportsDescribedValuesValues() { + final PropertyDescriptor descriptor = builder().defaultValue(EnumDescribedValue.GREEN).build(); + + assertEquals(EnumDescribedValue.GREEN.getValue(), descriptor.getDefaultValue()); + } + + /** + * Regression test for issue NIFI-49, to ensure that if a Processor's Property's + * Default Value is not allowed, the Exception thrown should indicate what the default value is + */ + @Test + void throwsIllegalStateExceptionWhenDefaultValueNotInAllowableValues() { + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + builder().allowableValues("NOT DEFAULT", "OTHER NOT DEFAULT").defaultValue(DEFAULT_VALUE).build(); + }); + assertTrue(exception.getMessage().contains("[" + DEFAULT_VALUE + "]")); + } + + @Test + void canBeCleared() { + final PropertyDescriptor descriptorWithDefault = builder().defaultValue(DEFAULT_VALUE).build(); + final PropertyDescriptor resetDescriptor = builder(descriptorWithDefault).clearDefaultValue().build(); + + assertNull(resetDescriptor.getDefaultValue()); + } } - @Test - void testExceptionThrownByDescriptorWithInvalidDefaultValue() { - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> invalidDescriptorBuilder.build()); - assertTrue(exception.getMessage().contains("[" + DEFAULT_VALUE + "]") ); - } + @Nested + class RegardingAllowableValues { - @Test - void testNoExceptionThrownByPropertyDescriptorWithValidDefaultValue() { - assertNotNull(validDescriptorBuilder.build()); - } + private static final Comparator allowableValueComparator = Comparator.comparing(AllowableValue::getValue); + private final List expectedMinimalAllowableValues = + List.of(new AllowableValue("GREEN"), new AllowableValue("RED"), new AllowableValue("BLUE")); + private final List expectedAllowableValuesWithDescription = + Arrays.stream(EnumDescribedValue.values()).map(AllowableValue::fromDescribedValue).toList(); - @Test - void testDefaultValueWithDescribedValue() { - final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder() - .name("defaultDescribedValueDescriptor") - .defaultValue(EnumAllowableValue.GREEN) - .build(); + @Test + void supportsStringVarArgValues() { + final List expected = expectedMinimalAllowableValues; - assertNotNull(propertyDescriptor); - assertEquals(EnumAllowableValue.GREEN.getValue(), propertyDescriptor.getDefaultValue()); - } + final PropertyDescriptor descriptor = builder().allowableValues("GREEN", "RED", "BLUE").build(); + final List actual = descriptor.getAllowableValues(); - @Test - void testAllowableValuesWithEnumClass() { - final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder() - .name("enumAllowableValueDescriptor") - .allowableValues(EnumAllowableValue.class) - .build(); + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } - assertNotNull(propertyDescriptor); + @Test + void supportsStringSetValues() { + final List expected = sort(expectedMinimalAllowableValues); - final List expectedAllowableValues = Arrays.stream(EnumAllowableValue.values()) - .map(enumValue -> new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription())) - .collect(Collectors.toList()); - assertEquals(expectedAllowableValues, propertyDescriptor.getAllowableValues()); - } + final PropertyDescriptor descriptor = builder().allowableValues(Set.of("GREEN", "RED", "BLUE")).build(); + // the iteration order of sets is not guaranteed by all implementations, thus we unify the order here + final List actual = sort(descriptor.getAllowableValues()); - @Test - void testAllowableValuesWithEnumSet() { - final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder() - .name("enumAllowableValueDescriptor") - .allowableValues(EnumSet.of( - EnumAllowableValue.GREEN, - EnumAllowableValue.BLUE - )) - .build(); + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } - assertNotNull(propertyDescriptor); + @Test + void supportsEnumArrayValues() { + final List expected = expectedMinimalAllowableValues; - final List expectedAllowableValues = Stream.of(EnumAllowableValue.GREEN, EnumAllowableValue.BLUE) - .map(enumValue -> new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription())) - .collect(Collectors.toList()); - assertEquals(expectedAllowableValues, propertyDescriptor.getAllowableValues()); + final PropertyDescriptor descriptor = builder().allowableValues(EnumNotDescribedValue.values()).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void supportsDescribedValueEnumArrayValues() { + final List expected = expectedAllowableValuesWithDescription; + + final Enum[] enumArray = EnumDescribedValue.values(); + final PropertyDescriptor descriptor = builder().allowableValues(enumArray).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsEnumClassValues() { + final List expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumNotDescribedValue.class).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueEnumClassValues() { + final List expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumDescribedValue.class).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsEnumSetValues() { + final List expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumSet.allOf(EnumNotDescribedValue.class)).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueEnumSetValues() { + final List expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumSet.allOf(EnumDescribedValue.class)).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueVarArgValues() { + final List expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder() + .allowableValues(EnumDescribedValue.GREEN, EnumDescribedValue.RED, EnumDescribedValue.BLUE).build(); + final List actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + private List sort(final List allowableValues) { + return allowableValues.stream().sorted(allowableValueComparator).toList(); + } + + private List displayNamesOf(final List allowableValues) { + return allowableValues.stream().map(AllowableValue::getDisplayName).toList(); + } + + private List descriptionsOf(final List allowableValues) { + return allowableValues.stream().map(AllowableValue::getDescription).toList(); + } } @Test @@ -121,7 +223,7 @@ public class TestPropertyDescriptor { final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder() .name("enumDependsOnDescriptor") - .dependsOn(dependentPropertyDescriptor, EnumAllowableValue.RED) + .dependsOn(dependentPropertyDescriptor, EnumDescribedValue.RED) .build(); assertNotNull(propertyDescriptor); @@ -133,17 +235,17 @@ public class TestPropertyDescriptor { final Set dependentValues = dependency.getDependentValues(); assertEquals(1, dependentValues.size()); final String dependentValue = dependentValues.iterator().next(); - assertEquals(EnumAllowableValue.RED.getValue(), dependentValue); + assertEquals(EnumDescribedValue.RED.getValue(), dependentValue); } @Test void testExternalResourceIgnoredIfELWithAttributesPresent() { final PropertyDescriptor descriptor = new PropertyDescriptor.Builder() - .name("dir") - .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE) - .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) - .required(false) - .build(); + .name("dir") + .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE) + .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) + .required(false) + .build(); final ValidationContext validationContext = Mockito.mock(ValidationContext.class); Mockito.when(validationContext.isExpressionLanguagePresent(anyString())).thenReturn(true); @@ -159,11 +261,11 @@ public class TestPropertyDescriptor { @Test void testExternalResourceConsideredIfELVarRegistryPresent() { final PropertyDescriptor descriptor = new PropertyDescriptor.Builder() - .name("dir") - .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY) - .expressionLanguageSupported(ExpressionLanguageScope.ENVIRONMENT) - .required(false) - .build(); + .name("dir") + .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY) + .expressionLanguageSupported(ExpressionLanguageScope.ENVIRONMENT) + .required(false) + .build(); final AtomicReference variable = new AtomicReference<>("__my_var__"); final ValidationContext validationContext = Mockito.mock(ValidationContext.class); @@ -190,9 +292,9 @@ public class TestPropertyDescriptor { // Consider if Expression Language is not supported. Mockito.when(validationContext.isExpressionLanguageSupported(anyString())).thenReturn(false); final PropertyDescriptor withElNotAllowed = new PropertyDescriptor.Builder() - .fromPropertyDescriptor(descriptor) - .expressionLanguageSupported(ExpressionLanguageScope.NONE) - .build(); + .fromPropertyDescriptor(descriptor) + .expressionLanguageSupported(ExpressionLanguageScope.NONE) + .build(); // Expression will not be evaluated, so the directory being looked at will literally be ${TestPropertyDescriptor.Var1} assertFalse(withElNotAllowed.validate("${TestPropertyDescriptor.Var1}", validationContext).isValid()); @@ -232,4 +334,12 @@ public class TestPropertyDescriptor { assertTrue(pd2.getDependencies().isEmpty()); assertNull(pd2.getAllowableValues()); } + + private Builder builder() { + return new PropertyDescriptor.Builder().name("propertyName"); + } + + private Builder builder(final PropertyDescriptor propertyDescriptor) { + return new PropertyDescriptor.Builder().fromPropertyDescriptor(propertyDescriptor); + } }