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;

Reply via email to