This is an automated email from the ASF dual-hosted git repository.

pvillard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 671f07e444 NIFI-14382 - Create processor to list box metadata 
templates for file
671f07e444 is described below

commit 671f07e444fd98d8f3ea88fc87a90c5c82f438db
Author: Noah Cover <[email protected]>
AuthorDate: Thu Mar 20 19:31:31 2025 -0700

    NIFI-14382 - Create processor to list box metadata templates for file
    
    Signed-off-by: Pierre Villard <[email protected]>
    
    This closes #9814.
---
 .../processors/box/BoxMetadataJsonArrayWriter.java | 111 ++++++++++
 .../box/ListBoxFileMetadataTemplates.java          | 228 +++++++++++++++++++++
 .../services/org.apache.nifi.processor.Processor   |   1 +
 .../box/BoxMetadataJsonArrayWriterTest.java        | 137 +++++++++++++
 .../box/ListBoxFileMetadataTemplatesTest.java      | 173 ++++++++++++++++
 5 files changed, 650 insertions(+)

diff --git 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriter.java
 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriter.java
new file mode 100644
index 0000000000..fbbefdf5b0
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.processors.box;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonObject;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * A class responsible for writing metadata objects into a JSON array.
+ */
+final class BoxMetadataJsonArrayWriter implements Closeable {
+
+    private final Writer writer;
+    private boolean hasBegun;
+    private boolean hasEntries;
+    private boolean closed;
+
+    private BoxMetadataJsonArrayWriter(final Writer writer) {
+        this.writer = writer;
+        this.hasBegun = false;
+        this.hasEntries = false;
+        this.closed = false;
+    }
+
+    static BoxMetadataJsonArrayWriter create(final OutputStream outputStream) 
throws IOException {
+        final Writer writer = new OutputStreamWriter(outputStream, UTF_8);
+        return new BoxMetadataJsonArrayWriter(writer);
+    }
+
+    void write(final Map<String, Object> templateFields) throws IOException {
+        if (closed) {
+            throw new IOException("The Writer is closed");
+        }
+
+        if (!hasBegun) {
+            beginArray();
+            hasBegun = true;
+        }
+
+        if (hasEntries) {
+            writer.write(',');
+        }
+
+        final JsonObject json = toRecord(templateFields);
+        json.writeTo(writer);
+
+        hasEntries = true;
+    }
+
+    private JsonObject toRecord(final Map<String, Object> templateFields) {
+        final JsonObject json = Json.object();
+
+        for (Map.Entry<String, Object> entry : templateFields.entrySet()) {
+            Object value = entry.getValue();
+            if (value == null) {
+                json.add(entry.getKey(), Json.NULL);
+            } else {
+                json.add(entry.getKey(), Json.value(value.toString()));
+            }
+        }
+
+        return json;
+    }
+
+    private void beginArray() throws IOException {
+        writer.write('[');
+    }
+
+    private void endArray() throws IOException {
+        writer.write(']');
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (closed) {
+            return;
+        }
+
+        closed = true;
+
+        if (!hasBegun) {
+            beginArray();
+        }
+        endArray();
+
+        writer.close();
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplates.java
 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplates.java
new file mode 100644
index 0000000000..559a6d859b
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplates.java
@@ -0,0 +1,228 @@
+/*
+ * 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.processors.box;
+
+import com.box.sdk.BoxAPIConnection;
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFile;
+import com.box.sdk.Metadata;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnScheduled;
+import org.apache.nifi.box.controllerservices.BoxClientService;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.lang.String.valueOf;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_CODE;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_CODE_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_MESSAGE;
+import static 
org.apache.nifi.processors.box.BoxFileAttributes.ERROR_MESSAGE_DESC;
+
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"box", "storage", "metadata", "templates"})
+@CapabilityDescription("Retrieves all metadata templates associated with a Box 
file.")
+@SeeAlso({ListBoxFile.class, FetchBoxFile.class, FetchBoxFileInfo.class})
+@WritesAttributes({
+        @WritesAttribute(attribute = "box.file.id", description = "The ID of 
the file from which metadata was fetched"),
+        @WritesAttribute(attribute = "record.count", description = "The number 
of records in the FlowFile"),
+        @WritesAttribute(attribute = "mime.type", description = "The MIME Type 
specified by the Record Writer"),
+        @WritesAttribute(attribute = "box.metadata.templates.names", 
description = "Comma-separated list of template names"),
+        @WritesAttribute(attribute = "box.metadata.templates.count", 
description = "Number of metadata templates found"),
+        @WritesAttribute(attribute = ERROR_CODE, description = 
ERROR_CODE_DESC),
+        @WritesAttribute(attribute = ERROR_MESSAGE, description = 
ERROR_MESSAGE_DESC)
+})
+public class ListBoxFileMetadataTemplates extends AbstractProcessor {
+
+    public static final PropertyDescriptor FILE_ID = new 
PropertyDescriptor.Builder()
+            .name("File ID")
+            .description("The ID of the file for which to fetch metadata.")
+            .required(true)
+            .defaultValue("${box.id}")
+            
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+    public static final Relationship REL_SUCCESS = new Relationship.Builder()
+            .name("success")
+            .description("A FlowFile containing the metadata template records 
will be routed to this relationship upon successful processing.")
+            .build();
+
+    public static final Relationship REL_FAILURE = new Relationship.Builder()
+            .name("failure")
+            .description("A FlowFile will be routed here if there is an error 
fetching metadata templates from the file.")
+            .build();
+
+    public static final Relationship REL_NOT_FOUND = new Relationship.Builder()
+            .name("not found")
+            .description("FlowFiles for which the specified Box file was not 
found will be routed to this relationship.")
+            .build();
+
+    public static final Set<Relationship> RELATIONSHIPS = Set.of(
+            REL_SUCCESS,
+            REL_FAILURE,
+            REL_NOT_FOUND
+    );
+
+    private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
List.of(
+            BoxClientService.BOX_CLIENT_SERVICE,
+            FILE_ID
+    );
+
+    private volatile BoxAPIConnection boxAPIConnection;
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return PROPERTY_DESCRIPTORS;
+    }
+
+    @Override
+    public Set<Relationship> getRelationships() {
+        return RELATIONSHIPS;
+    }
+
+    @OnScheduled
+    public void onScheduled(final ProcessContext context) {
+        final BoxClientService boxClientService = 
context.getProperty(BoxClientService.BOX_CLIENT_SERVICE)
+                .asControllerService(BoxClientService.class);
+        boxAPIConnection = boxClientService.getBoxApiConnection();
+    }
+
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession 
session) throws ProcessException {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String fileId = 
context.getProperty(FILE_ID).evaluateAttributeExpressions(flowFile).getValue();
+
+        try {
+            final BoxFile boxFile = getBoxFile(fileId);
+
+            final List<Map<String, Object>> templatesList = new ArrayList<>();
+            final Iterable<Metadata> metadataList = boxFile.getAllMetadata();
+            final Iterator<Metadata> iterator = metadataList.iterator();
+            final Set<String> templateNames = new LinkedHashSet<>();
+
+            if (!iterator.hasNext()) {
+                flowFile = session.putAttribute(flowFile, "box.file.id", 
fileId);
+                flowFile = session.putAttribute(flowFile, 
"box.metadata.templates.count", "0");
+                session.transfer(flowFile, REL_SUCCESS);
+                return;
+            }
+
+            while (iterator.hasNext()) {
+                final Metadata metadata = iterator.next();
+                final Map<String, Object> templateFields = new HashMap<>();
+
+                templateNames.add(metadata.getTemplateName());
+
+                // Add standard metadata fields
+                templateFields.put("$id", metadata.getID());
+                templateFields.put("$type", metadata.getTypeName());
+                templateFields.put("$parent", "file_" + fileId); // match the 
Box API format
+                templateFields.put("$template", metadata.getTemplateName());
+                templateFields.put("$scope", metadata.getScope());
+
+                // Add all dynamic fields from the metadata
+                for (final String fieldName : metadata.getPropertyPaths()) {
+                    if (metadata.getValue(fieldName) != null) {
+                        String cleanFieldName = fieldName.startsWith("/") ? 
fieldName.substring(1) : fieldName;
+                        String fieldValue = 
metadata.getValue(fieldName).asString();
+                        templateFields.put(cleanFieldName, fieldValue);
+                    }
+                }
+
+                templatesList.add(templateFields);
+            }
+
+            try {
+                try (final OutputStream out = session.write(flowFile);
+                     final BoxMetadataJsonArrayWriter writer = 
BoxMetadataJsonArrayWriter.create(out)) {
+
+                    // Write each metadata template as a separate JSON object 
in the array
+                    for (Map<String, Object> templateFields : templatesList) {
+                        writer.write(templateFields);
+                    }
+                }
+
+                final Map<String, String> recordAttributes = new HashMap<>();
+                recordAttributes.put("record.count", 
String.valueOf(templatesList.size()));
+                recordAttributes.put(CoreAttributes.MIME_TYPE.key(), 
"application/json");
+                recordAttributes.put("box.file.id", fileId);
+                recordAttributes.put("box.metadata.templates.names", 
String.join(",", templateNames));
+                recordAttributes.put("box.metadata.templates.count", 
String.valueOf(templatesList.size()));
+                flowFile = session.putAllAttributes(flowFile, 
recordAttributes);
+
+                session.getProvenanceReporter().receive(flowFile, 
BoxFileUtils.BOX_URL + fileId);
+                session.transfer(flowFile, REL_SUCCESS);
+            } catch (final IOException e) {
+                getLogger().error("Failed writing metadata templates from file 
[{}]", fileId, e);
+                flowFile = session.putAttribute(flowFile, ERROR_MESSAGE, 
e.getMessage());
+                session.transfer(flowFile, REL_FAILURE);
+            }
+
+        } catch (final BoxAPIResponseException e) {
+            flowFile = session.putAttribute(flowFile, ERROR_CODE, 
valueOf(e.getResponseCode()));
+            flowFile = session.putAttribute(flowFile, ERROR_MESSAGE, 
e.getMessage());
+            if (e.getResponseCode() == 404) {
+                getLogger().warn("Box file with ID {} was not found.", fileId);
+                session.transfer(flowFile, REL_NOT_FOUND);
+            } else {
+                getLogger().error("Couldn't fetch metadata templates from file 
with id [{}]", fileId, e);
+                session.transfer(flowFile, REL_FAILURE);
+            }
+        } catch (final Exception e) {
+            getLogger().error("Failed to process metadata templates for file 
[{}]", fileId, e);
+            flowFile = session.putAttribute(flowFile, ERROR_MESSAGE, 
e.getMessage());
+            session.transfer(flowFile, REL_FAILURE);
+        }
+    }
+
+    /**
+     * Returns a BoxFile object for the given file ID.
+     *
+     * @param fileId The ID of the file.
+     * @return A BoxFile object for the given file ID.
+     */
+    BoxFile getBoxFile(final String fileId) {
+        return new BoxFile(boxAPIConnection, fileId);
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index cf5335907b..d2c21746e2 100644
--- 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -22,4 +22,5 @@ org.apache.nifi.processors.box.GetBoxFileCollaborators
 org.apache.nifi.processors.box.GetBoxGroupMembers
 org.apache.nifi.processors.box.ListBoxFile
 org.apache.nifi.processors.box.ListBoxFileInfo
+org.apache.nifi.processors.box.ListBoxFileMetadataTemplates
 org.apache.nifi.processors.box.PutBoxFile
diff --git 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriterTest.java
 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriterTest.java
new file mode 100644
index 0000000000..f58d0e19cb
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxMetadataJsonArrayWriterTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.processors.box;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonArray;
+import com.eclipsesource.json.JsonObject;
+import com.eclipsesource.json.JsonValue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BoxMetadataJsonArrayWriterTest {
+
+    private ByteArrayOutputStream out;
+    private BoxMetadataJsonArrayWriter writer;
+
+    @BeforeEach
+    void setUp() throws IOException {
+        out = new ByteArrayOutputStream();
+        writer = BoxMetadataJsonArrayWriter.create(out);
+    }
+
+    @Test
+    void writeNoMetadata() throws IOException {
+        writer.close();
+
+        assertEquals(Json.array(), actualJson());
+    }
+
+    @Test
+    void writeSingleMetadata() throws IOException {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("$id", "test-metadata-id-1");
+        metadata.put("$type", "test-metadata-type");
+        metadata.put("$scope", "enterprise");
+        metadata.put("$template", "testTemplate");
+        metadata.put("$parent", "file_12345");
+        metadata.put("testField1", "value1");
+        metadata.put("testField2", "value2");
+
+        writer.write(metadata);
+        writer.close();
+
+        JsonArray resultArray = actualJson().asArray();
+        assertEquals(1, resultArray.size());
+
+        JsonObject resultObject = resultArray.get(0).asObject();
+        assertEquals("test-metadata-id-1", resultObject.get("$id").asString());
+        assertEquals("test-metadata-type", 
resultObject.get("$type").asString());
+        assertEquals("enterprise", resultObject.get("$scope").asString());
+        assertEquals("testTemplate", resultObject.get("$template").asString());
+        assertEquals("file_12345", resultObject.get("$parent").asString());
+        assertEquals("value1", resultObject.get("testField1").asString());
+        assertEquals("value2", resultObject.get("testField2").asString());
+    }
+
+    @Test
+    void writeMultipleMetadata() throws IOException {
+        Map<String, Object> metadata1 = new HashMap<>();
+        metadata1.put("$id", "test-metadata-id-1");
+        metadata1.put("$type", "test-type-1");
+        metadata1.put("$scope", "enterprise");
+        metadata1.put("$template", "testTemplate1");
+        metadata1.put("$parent", "file_12345");
+        metadata1.put("field1", "value1");
+        metadata1.put("field2", "value2");
+
+        Map<String, Object> metadata2 = new HashMap<>();
+        metadata2.put("$id", "test-metadata-id-2");
+        metadata2.put("$type", "test-type-2");
+        metadata2.put("$scope", "global");
+        metadata2.put("$template", "testTemplate2");
+        metadata2.put("$parent", "file_12345");
+        metadata2.put("field3", "value3");
+        metadata2.put("field4", "value4");
+
+        writer.write(metadata1);
+        writer.write(metadata2);
+        writer.close();
+
+        JsonArray resultArray = actualJson().asArray();
+        assertEquals(2, resultArray.size());
+
+        Set<String> foundIds = new HashSet<>();
+        for (JsonValue value : resultArray) {
+            JsonObject obj = value.asObject();
+            String id = obj.get("$id").asString();
+            foundIds.add(id);
+            if (id.equals("test-metadata-id-1")) {
+                assertEquals("test-type-1", obj.get("$type").asString());
+                assertEquals("enterprise", obj.get("$scope").asString());
+                assertEquals("testTemplate1", obj.get("$template").asString());
+                assertEquals("file_12345", obj.get("$parent").asString());
+                assertEquals("value1", obj.get("field1").asString());
+                assertEquals("value2", obj.get("field2").asString());
+            } else if (id.equals("test-metadata-id-2")) {
+                assertEquals("test-type-2", obj.get("$type").asString());
+                assertEquals("global", obj.get("$scope").asString());
+                assertEquals("testTemplate2", obj.get("$template").asString());
+                assertEquals("file_12345", obj.get("$parent").asString());
+                assertEquals("value3", obj.get("field3").asString());
+                assertEquals("value4", obj.get("field4").asString());
+            }
+        }
+        assertEquals(2, foundIds.size());
+        assertTrue(foundIds.contains("test-metadata-id-1"));
+        assertTrue(foundIds.contains("test-metadata-id-2"));
+    }
+
+    private JsonValue actualJson() {
+        return Json.parse(out.toString());
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplatesTest.java
 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplatesTest.java
new file mode 100644
index 0000000000..e95c85593b
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataTemplatesTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.processors.box;
+
+import com.box.sdk.BoxAPIException;
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFile;
+import com.box.sdk.Metadata;
+import com.eclipsesource.json.JsonValue;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunners;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class ListBoxFileMetadataTemplatesTest extends AbstractBoxFileTest {
+
+    private static final String TEMPLATE_1_ID = "12345";
+    private static final String TEMPLATE_1_NAME = "fileMetadata";
+    private static final String TEMPLATE_1_SCOPE = "enterprise_123";
+    private static final String TEMPLATE_2_ID = "67890";
+    private static final String TEMPLATE_2_NAME = "properties";
+    private static final String TEMPLATE_2_SCOPE = "global";
+
+    @Mock
+    private BoxFile mockBoxFile;
+
+    @Mock
+    private Metadata mockMetadata1;
+
+    @Mock
+    private Metadata mockMetadata2;
+
+    @Override
+    @BeforeEach
+    void setUp() throws Exception {
+        final ListBoxFileMetadataTemplates testSubject = new 
ListBoxFileMetadataTemplates() {
+            @Override
+            BoxFile getBoxFile(String fileId) {
+                return mockBoxFile;
+            }
+        };
+
+        testRunner = TestRunners.newTestRunner(testSubject);
+        super.setUp();
+    }
+
+    @Test
+    void testSuccessfulMetadataRetrieval() {
+        final List<Metadata> metadataList = new ArrayList<>();
+        metadataList.add(mockMetadata1);
+        metadataList.add(mockMetadata2);
+        JsonValue mockJsonValue1 = mock(JsonValue.class);
+        JsonValue mockJsonValue2 = mock(JsonValue.class);
+        JsonValue mockJsonValue3 = mock(JsonValue.class);
+        JsonValue mockJsonValue4 = mock(JsonValue.class);
+
+        when(mockJsonValue1.asString()).thenReturn("document.pdf");
+        when(mockJsonValue2.asString()).thenReturn("pdf");
+        when(mockJsonValue3.asString()).thenReturn("Test Document");
+        when(mockJsonValue4.asString()).thenReturn("John Doe");
+
+        // Template 1 setup (fileMetadata)
+        when(mockMetadata1.getID()).thenReturn(TEMPLATE_1_ID);
+        when(mockMetadata1.getTemplateName()).thenReturn(TEMPLATE_1_NAME);
+        when(mockMetadata1.getScope()).thenReturn(TEMPLATE_1_SCOPE);
+        List<String> template1Fields = List.of("fileName", "fileExtension");
+        when(mockMetadata1.getPropertyPaths()).thenReturn(template1Fields);
+        when(mockMetadata1.getValue("fileName")).thenReturn(mockJsonValue1);
+        
when(mockMetadata1.getValue("fileExtension")).thenReturn(mockJsonValue2);
+
+        // Template 2 setup (properties)
+        when(mockMetadata2.getID()).thenReturn(TEMPLATE_2_ID);
+        when(mockMetadata2.getTemplateName()).thenReturn(TEMPLATE_2_NAME);
+        when(mockMetadata2.getScope()).thenReturn(TEMPLATE_2_SCOPE);
+
+        List<String> template2Fields = List.of("Test Number", "Title", 
"Author", "Date");
+        when(mockMetadata2.getPropertyPaths()).thenReturn(template2Fields);
+        when(mockMetadata2.getValue("Test Number")).thenReturn(null); // Test 
null handling
+        when(mockMetadata2.getValue("Title")).thenReturn(mockJsonValue3);
+        when(mockMetadata2.getValue("Author")).thenReturn(mockJsonValue4);
+        doReturn(metadataList).when(mockBoxFile).getAllMetadata();
+
+        testRunner.setProperty(ListBoxFileMetadataTemplates.FILE_ID, 
TEST_FILE_ID);
+        testRunner.enqueue(new byte[0]);
+        testRunner.run();
+        
testRunner.assertAllFlowFilesTransferred(ListBoxFileMetadataTemplates.REL_SUCCESS,
 1);
+
+        final MockFlowFile flowFile = 
testRunner.getFlowFilesForRelationship(ListBoxFileMetadataTemplates.REL_SUCCESS).getFirst();
+        flowFile.assertAttributeEquals("box.file.id", TEST_FILE_ID);
+        flowFile.assertAttributeEquals("record.count", "2");
+        flowFile.assertAttributeEquals("box.metadata.templates.names", 
"fileMetadata,properties");
+        flowFile.assertAttributeEquals("box.metadata.templates.count", "2");
+
+        String content = new String(flowFile.toByteArray());
+        testRunner.getLogger().info("FlowFile content: {}", content);
+
+        // Check that content contains key elements
+        
org.junit.jupiter.api.Assertions.assertTrue(content.contains("\"$id\""));
+        
org.junit.jupiter.api.Assertions.assertTrue(content.contains("\"$template\""));
+        
org.junit.jupiter.api.Assertions.assertTrue(content.contains("\"$scope\""));
+        org.junit.jupiter.api.Assertions.assertTrue(content.contains("["));
+        org.junit.jupiter.api.Assertions.assertTrue(content.contains("]"));
+    }
+
+    @Test
+    void testNoMetadata() {
+        when(mockBoxFile.getAllMetadata()).thenReturn(new ArrayList<>());
+        testRunner.setProperty(ListBoxFileMetadataTemplates.FILE_ID, 
TEST_FILE_ID);
+        testRunner.enqueue(new byte[0]);
+        testRunner.run();
+
+        
testRunner.assertAllFlowFilesTransferred(ListBoxFileMetadataTemplates.REL_SUCCESS,
 1);
+        final MockFlowFile flowFile = 
testRunner.getFlowFilesForRelationship(ListBoxFileMetadataTemplates.REL_SUCCESS).getFirst();
+        flowFile.assertAttributeEquals("box.file.id", TEST_FILE_ID);
+        flowFile.assertAttributeEquals("box.metadata.templates.count", "0");
+    }
+
+    @Test
+    void testFileNotFound() {
+        BoxAPIResponseException mockException = new 
BoxAPIResponseException("API Error", 404, "Box File Not Found", null);
+        doThrow(mockException).when(mockBoxFile).getAllMetadata();
+
+        testRunner.setProperty(ListBoxFileMetadataTemplates.FILE_ID, 
TEST_FILE_ID);
+        testRunner.enqueue(new byte[0]);
+        testRunner.run();
+
+        
testRunner.assertAllFlowFilesTransferred(ListBoxFileMetadataTemplates.REL_NOT_FOUND,
 1);
+        final MockFlowFile flowFile = 
testRunner.getFlowFilesForRelationship(ListBoxFileMetadataTemplates.REL_NOT_FOUND).getFirst();
+        flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_CODE, "404");
+        flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE, "API 
Error [404]");
+    }
+
+    @Test
+    void testBoxApiException() {
+        BoxAPIException mockException = new BoxAPIException("General API 
Error", 500, "Unexpected Error");
+        doThrow(mockException).when(mockBoxFile).getAllMetadata();
+
+        testRunner.setProperty(ListBoxFileMetadataTemplates.FILE_ID, 
TEST_FILE_ID);
+        testRunner.enqueue(new byte[0]);
+        testRunner.run();
+
+        
testRunner.assertAllFlowFilesTransferred(ListBoxFileMetadataTemplates.REL_FAILURE,
 1);
+        final MockFlowFile flowFile = 
testRunner.getFlowFilesForRelationship(ListBoxFileMetadataTemplates.REL_FAILURE).getFirst();
+        flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE, 
"General API Error\nUnexpected Error");
+    }
+
+}

Reply via email to