This is an automated email from the ASF dual-hosted git repository. markap14 pushed a commit to branch NIFI-15258 in repository https://gitbox.apache.org/repos/asf/nifi-api.git
commit 39c3c15dd04935e67aacf9ab3837c41d2179dd93 Author: Bryan Bende <[email protected]> AuthorDate: Wed Dec 17 16:28:41 2025 -0500 NIFI-15315 Add support for assets in Connectors (#31) - Add ownerIdentifier in Asset and deprecate parameterContextIdentifier - Add ASSET and ASSET_LIST types for Connector properties and update validation - Support multiple asset references Signed-off-by: Kevin Doran <[email protected]> --- src/main/java/org/apache/nifi/asset/Asset.java | 9 ++ .../nifi/components/connector/AssetReference.java | 25 +++-- .../connector/ConnectorPropertyDescriptor.java | 53 ++++++++-- .../nifi/components/connector/PropertyType.java | 4 +- .../flow/VersionedConnectorValueReference.java | 25 ++--- .../connector/TestConnectorPropertyDescriptor.java | 114 ++++++++++++++++++++- .../org/apache/nifi/parameter/TestParameter.java | 5 + 7 files changed, 203 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/apache/nifi/asset/Asset.java b/src/main/java/org/apache/nifi/asset/Asset.java index 3e8c026..2ea52b2 100644 --- a/src/main/java/org/apache/nifi/asset/Asset.java +++ b/src/main/java/org/apache/nifi/asset/Asset.java @@ -37,9 +37,18 @@ public interface Asset { * Returns the identifier of the parameter context the Asset belongs to * * @return Parameter Context Identifier + * @deprecated Use {@link #getOwnerIdentifier()} instead */ + @Deprecated String getParameterContextIdentifier(); + /** + * Returns the identifier of the resource the Asset belongs to + * + * @return Owner Identifier + */ + String getOwnerIdentifier(); + /** * Returns the name of the Asset * diff --git a/src/main/java/org/apache/nifi/components/connector/AssetReference.java b/src/main/java/org/apache/nifi/components/connector/AssetReference.java index 13abf73..1115745 100644 --- a/src/main/java/org/apache/nifi/components/connector/AssetReference.java +++ b/src/main/java/org/apache/nifi/components/connector/AssetReference.java @@ -17,26 +17,29 @@ package org.apache.nifi.components.connector; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; /** - * A ConnectorValueReference implementation representing a reference to an asset. + * A ConnectorValueReference implementation representing a reference to one or more assets. */ public final class AssetReference implements ConnectorValueReference { - private final String assetIdentifier; + private final Set<String> assetIdentifiers; - public AssetReference(final String assetIdentifier) { - this.assetIdentifier = assetIdentifier; + public AssetReference(final Set<String> assetIdentifiers) { + this.assetIdentifiers = assetIdentifiers == null ? Collections.emptySet() : new HashSet<>(assetIdentifiers); } /** - * Returns the asset identifier. + * Returns the asset identifiers. * - * @return the asset identifier + * @return the asset identifiers */ - public String getAssetIdentifier() { - return assetIdentifier; + public Set<String> getAssetIdentifiers() { + return assetIdentifiers; } @Override @@ -53,16 +56,16 @@ public final class AssetReference implements ConnectorValueReference { return false; } final AssetReference that = (AssetReference) object; - return Objects.equals(assetIdentifier, that.assetIdentifier); + return Objects.equals(assetIdentifiers, that.assetIdentifiers); } @Override public int hashCode() { - return Objects.hashCode(assetIdentifier); + return Objects.hashCode(assetIdentifiers); } @Override public String toString() { - return "AssetReference[assetId=" + assetIdentifier + "]"; + return "AssetReference[assetIds=" + assetIdentifiers + "]"; } } diff --git a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java index 209039b..8debeb4 100644 --- a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java +++ b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java @@ -22,6 +22,7 @@ import org.apache.nifi.components.DescribedValue; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.Validator; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -113,19 +114,23 @@ public final class ConnectorPropertyDescriptor { fetchedAllowableValues = null; } - if (type != PropertyType.STRING_LIST) { - return validateIndividual(stepName, groupName, value, validationContext, fetchedAllowableValues); - } - if (required && value == null) { return new ValidationResult.Builder() .subject(name) - .input(value) + .input(null) .valid(false) .explanation("Property is required but no value was specified") .build(); } + if (type == PropertyType.ASSET || type == PropertyType.ASSET_LIST) { + return validateAssets(value); + } + + if (type != PropertyType.STRING_LIST) { + return validateIndividual(stepName, groupName, value, validationContext, fetchedAllowableValues); + } + final String[] values = value.split(","); for (final String individualValue : values) { final ValidationResult result = validateIndividual(stepName, groupName, individualValue.trim(), validationContext, fetchedAllowableValues); @@ -190,10 +195,9 @@ public final class ConnectorPropertyDescriptor { return false; } - private ValidationResult validateType(final String value) { final String explanation = switch (type) { - case SECRET, STRING, STRING_LIST -> null; + case SECRET, STRING, STRING_LIST, ASSET, ASSET_LIST -> null; case BOOLEAN -> BOOLEAN_PATTERN.matcher(value).matches() ? null : "Value must be true or false"; case INTEGER -> INTEGER_PATTERN.matcher(value).matches() ? null : "Value must be an integer"; case DOUBLE, FLOAT -> DOUBLE_PATTERN.matcher(value).matches() ? null : "Value must be a floating point number"; @@ -211,6 +215,41 @@ public final class ConnectorPropertyDescriptor { .build(); } + private ValidationResult validateAssets(final String value) { + final String[] values = value.split(","); + if (type == PropertyType.ASSET && values.length > 1) { + return new ValidationResult.Builder() + .subject(name) + .input(value) + .valid(false) + .explanation("Property only supports a single asset, but " + values.length + " assets were specified") + .build(); + } + + final List<String> nonExistentAssets = new ArrayList<>(); + for (final String assetValue : values) { + final File assetFile = new File(assetValue); + if (!assetFile.exists() || !assetFile.canRead()) { + nonExistentAssets.add(assetValue); + } + } + + if (!nonExistentAssets.isEmpty()) { + return new ValidationResult.Builder() + .subject(name) + .input(value) + .valid(false) + .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentAssets) + .build(); + } + + return new ValidationResult.Builder() + .subject(name) + .input(value) + .valid(true) + .build(); + } + @Override public boolean equals(final Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/src/main/java/org/apache/nifi/components/connector/PropertyType.java b/src/main/java/org/apache/nifi/components/connector/PropertyType.java index f01a91e..945b492 100644 --- a/src/main/java/org/apache/nifi/components/connector/PropertyType.java +++ b/src/main/java/org/apache/nifi/components/connector/PropertyType.java @@ -24,5 +24,7 @@ public enum PropertyType { FLOAT, DOUBLE, STRING_LIST, - SECRET + SECRET, + ASSET, + ASSET_LIST } diff --git a/src/main/java/org/apache/nifi/flow/VersionedConnectorValueReference.java b/src/main/java/org/apache/nifi/flow/VersionedConnectorValueReference.java index e9e881c..7bdc3b5 100644 --- a/src/main/java/org/apache/nifi/flow/VersionedConnectorValueReference.java +++ b/src/main/java/org/apache/nifi/flow/VersionedConnectorValueReference.java @@ -18,6 +18,7 @@ package org.apache.nifi.flow; import java.util.Objects; +import java.util.Set; /** * Represents a property value reference for a Connector in a versioned flow. @@ -27,11 +28,11 @@ import java.util.Objects; public class VersionedConnectorValueReference { private String valueType; private String value; - private String assetId; private String providerId; private String providerName; private String secretName; private String fullyQualifiedSecretName; + private Set<String> assetIds; public String getValueType() { return valueType; @@ -49,14 +50,6 @@ public class VersionedConnectorValueReference { this.value = value; } - public String getAssetId() { - return assetId; - } - - public void setAssetId(final String assetId) { - this.assetId = assetId; - } - public String getProviderId() { return providerId; } @@ -89,6 +82,14 @@ public class VersionedConnectorValueReference { this.fullyQualifiedSecretName = fullyQualifiedSecretName; } + public Set<String> getAssetIds() { + return assetIds; + } + + public void setAssetIds(final Set<String> assetIds) { + this.assetIds = assetIds; + } + @Override public boolean equals(final Object obj) { if (this == obj) { @@ -99,7 +100,7 @@ public class VersionedConnectorValueReference { } return Objects.equals(valueType, other.valueType) && Objects.equals(value, other.value) - && Objects.equals(assetId, other.assetId) + && Objects.equals(assetIds, other.assetIds) && Objects.equals(providerId, other.providerId) && Objects.equals(secretName, other.secretName) && Objects.equals(fullyQualifiedSecretName, other.fullyQualifiedSecretName); @@ -107,12 +108,12 @@ public class VersionedConnectorValueReference { @Override public int hashCode() { - return Objects.hash(valueType, value, assetId, providerId, secretName, fullyQualifiedSecretName); + return Objects.hash(valueType, value, assetIds, providerId, secretName, fullyQualifiedSecretName); } @Override public String toString() { return "VersionedConnectorValueReference[valueType=" + valueType + ", value=" + value - + ", assetId=" + assetId + ", providerId=" + providerId + ", fullyQualifiedSecretName=" + fullyQualifiedSecretName + "]"; + + ", assetIds=" + assetIds + ", providerId=" + providerId + ", fullyQualifiedSecretName=" + fullyQualifiedSecretName + "]"; } } diff --git a/src/test/java/org/apache/nifi/components/connector/TestConnectorPropertyDescriptor.java b/src/test/java/org/apache/nifi/components/connector/TestConnectorPropertyDescriptor.java index 823c3ef..ec6da58 100644 --- a/src/test/java/org/apache/nifi/components/connector/TestConnectorPropertyDescriptor.java +++ b/src/test/java/org/apache/nifi/components/connector/TestConnectorPropertyDescriptor.java @@ -21,7 +21,14 @@ import org.apache.nifi.components.DescribedValue; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.junit.jupiter.api.Test; - +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -34,6 +41,18 @@ public class TestConnectorPropertyDescriptor { private static final String TEST_STEP_NAME = "test-step"; private static final String TEST_GROUP_NAME = "test-group"; + @Test + void testRequiredProperty() { + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Required Property") + .type(PropertyType.STRING) + .required(true) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertFalse(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, null, context).isValid()); + } + @Test void testValidateStringType() { final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() @@ -210,6 +229,99 @@ public class TestConnectorPropertyDescriptor { assertFalse(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, ".123", context).isValid()); } + @Test + void testValidateAssetTypeWithExistingAsset(@TempDir final Path tempDir) throws IOException { + final File assetFile = createAssetFile(tempDir, "asset1.txt"); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset Property") + .type(PropertyType.ASSET) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertTrue(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, assetFile.getAbsolutePath(), context).isValid()); + } + + @Test + void testValidateAssetTypeWithMissingAsset(@TempDir final Path tempDir) throws IOException { + final File assetFile = new File(tempDir.toFile(), "asset1.txt"); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset Property") + .type(PropertyType.ASSET) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertFalse(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, assetFile.getAbsolutePath(), context).isValid()); + } + + @Test + void testValidateAssetTypeWithMultipleAssets(@TempDir final Path tempDir) throws IOException { + final File assetFile1 = new File(tempDir.toFile(), "asset1.txt"); + final File assetFile2 = new File(tempDir.toFile(), "asset2.txt"); + final String multipleAssetValue = assetFile1.getAbsolutePath() + "," + assetFile2.getAbsolutePath(); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset Property") + .type(PropertyType.ASSET) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertFalse(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, multipleAssetValue, context).isValid()); + } + + @Test + void testValidateAssetListTypeWithSingleExistingAsset(@TempDir final Path tempDir) throws IOException { + final File assetFile1 = createAssetFile(tempDir, "asset1.txt"); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset List Property") + .type(PropertyType.ASSET_LIST) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertTrue(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, assetFile1.getAbsolutePath(), context).isValid()); + } + + @Test + void testValidateAssetListTypeWithMultipleExistingAssets(@TempDir final Path tempDir) throws IOException { + final File assetFile1 = createAssetFile(tempDir, "asset1.txt"); + final File assetFile2 = createAssetFile(tempDir, "asset2.txt"); + final String multipleAssetValue = assetFile1.getAbsolutePath() + "," + assetFile2.getAbsolutePath(); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset List Property") + .type(PropertyType.ASSET_LIST) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertTrue(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, multipleAssetValue, context).isValid()); + } + + @Test + void testValidateAssetListTypeWithSomeMissingAssets(@TempDir final Path tempDir) throws IOException { + final File assetFile1 = createAssetFile(tempDir, "asset1.txt"); + final File assetFile2 = new File(tempDir.toFile(), "asset2.txt"); // Never created + final String multipleAssetValue = assetFile1.getAbsolutePath() + "," + assetFile2.getAbsolutePath(); + + final ConnectorPropertyDescriptor descriptor = new ConnectorPropertyDescriptor.Builder() + .name("Asset List Property") + .type(PropertyType.ASSET_LIST) + .build(); + + final ConnectorValidationContext context = new TestConnectorValidationContext(); + assertFalse(descriptor.validate(TEST_STEP_NAME, TEST_GROUP_NAME, multipleAssetValue, context).isValid()); + } + + private File createAssetFile(final Path parentDir, final String name) throws IOException { + final File assetFile = new File(parentDir.toFile(), name); + try (final OutputStream outputStream = new FileOutputStream(assetFile)) { + outputStream.write(name.getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + } + return assetFile; + } + /** * Simple test implementation of ConnectorValidationContext for unit testing. */ diff --git a/src/test/java/org/apache/nifi/parameter/TestParameter.java b/src/test/java/org/apache/nifi/parameter/TestParameter.java index 018d589..a7d62fb 100644 --- a/src/test/java/org/apache/nifi/parameter/TestParameter.java +++ b/src/test/java/org/apache/nifi/parameter/TestParameter.java @@ -253,6 +253,11 @@ public class TestParameter { return parameterContextIdentifier; } + @Override + public String getOwnerIdentifier() { + return parameterContextIdentifier; + } + @Override public String getName() { return name;
