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");
+ }
+
+}