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 3693d8af72 NIFI-14445: Creating processor to fetch specific metadata
instance for box file
3693d8af72 is described below
commit 3693d8af72e669ae2b97ee75630c0550ade64c8c
Author: Noah Cover <[email protected]>
AuthorDate: Mon Apr 7 17:49:35 2025 -0700
NIFI-14445: Creating processor to fetch specific metadata instance for box
file
Signed-off-by: Pierre Villard <[email protected]>
This closes #9856.
---
...nces.java => FetchBoxFileMetadataInstance.java} | 169 ++++++++-------------
.../box/ListBoxFileMetadataInstances.java | 56 +------
.../processors/box/utils/BoxMetadataUtils.java | 90 +++++++++++
.../services/org.apache.nifi.processor.Processor | 1 +
...cesParseJsonTest.java => BoxParseJsonTest.java} | 37 ++---
.../box/FetchBoxFileMetadataInstanceTest.java | 147 ++++++++++++++++++
6 files changed, 322 insertions(+), 178 deletions(-)
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstance.java
similarity index 56%
copy from
nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
copy to
nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstance.java
index 3c05d841a1..aff6e82182 100644
---
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstance.java
@@ -20,7 +20,6 @@ import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxAPIResponseException;
import com.box.sdk.BoxFile;
import com.box.sdk.Metadata;
-import com.eclipsesource.json.JsonValue;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
@@ -42,11 +41,7 @@ import org.apache.nifi.processor.util.StandardValidators;
import java.io.IOException;
import java.io.OutputStream;
-import java.math.BigDecimal;
-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;
@@ -56,21 +51,21 @@ 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;
+import static
org.apache.nifi.processors.box.utils.BoxMetadataUtils.processBoxMetadataInstance;
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
-@Tags({"box", "storage", "metadata", "instances", "templates"})
-@CapabilityDescription("Retrieves all metadata instances associated with a Box
file.")
-@SeeAlso({ListBoxFile.class, FetchBoxFile.class, FetchBoxFileInfo.class})
+@Tags({"box", "storage", "metadata", "instance", "template"})
+@CapabilityDescription("Retrieves specific metadata instance associated with a
Box file using template key and scope.")
+@SeeAlso({ListBoxFileMetadataInstances.class, ListBoxFile.class,
FetchBoxFile.class, FetchBoxFileInfo.class})
@WritesAttributes({
@WritesAttribute(attribute = "box.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.instances.names",
description = "Comma-separated list of instances names"),
- @WritesAttribute(attribute = "box.metadata.instances.count",
description = "Number of metadata instances found"),
+ @WritesAttribute(attribute = "box.metadata.template.key", description
= "The metadata template key"),
+ @WritesAttribute(attribute = "box.metadata.template.scope",
description = "The metadata template scope"),
+ @WritesAttribute(attribute = "mime.type", description = "The MIME Type
of the FlowFile content"),
@WritesAttribute(attribute = ERROR_CODE, description =
ERROR_CODE_DESC),
@WritesAttribute(attribute = ERROR_MESSAGE, description =
ERROR_MESSAGE_DESC)
})
-public class ListBoxFileMetadataInstances extends AbstractProcessor {
+public class FetchBoxFileMetadataInstance extends AbstractProcessor {
public static final PropertyDescriptor FILE_ID = new
PropertyDescriptor.Builder()
.name("File ID")
@@ -81,30 +76,55 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
+ public static final PropertyDescriptor TEMPLATE_KEY = new
PropertyDescriptor.Builder()
+ .name("Template Key")
+ .description("The metadata template key to retrieve.")
+ .required(true)
+
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final PropertyDescriptor TEMPLATE_SCOPE = new
PropertyDescriptor.Builder()
+ .name("Template Scope")
+ .description("The metadata template scope (e.g., 'enterprise',
'global').")
+ .required(true)
+ .defaultValue("enterprise")
+
.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 instances records
will be routed to this relationship upon successful processing.")
+ .description("A FlowFile containing the metadata instance 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 instances from the file.")
+ .description("A FlowFile will be routed here if there is an error
fetching metadata instance from the file.")
.build();
- public static final Relationship REL_NOT_FOUND = new Relationship.Builder()
- .name("not found")
+ public static final Relationship REL_FILE_NOT_FOUND = new
Relationship.Builder()
+ .name("file not found")
.description("FlowFiles for which the specified Box file was not
found will be routed to this relationship.")
.build();
+ public static final Relationship REL_TEMPLATE_NOT_FOUND = new
Relationship.Builder()
+ .name("template not found")
+ .description("FlowFiles for which the specified metadata template
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
+ REL_FILE_NOT_FOUND,
+ REL_TEMPLATE_NOT_FOUND
);
private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS =
List.of(
BoxClientService.BOX_CLIENT_SERVICE,
- FILE_ID
+ FILE_ID,
+ TEMPLATE_KEY,
+ TEMPLATE_SCOPE
);
private volatile BoxAPIConnection boxAPIConnection;
@@ -134,68 +154,35 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
}
final String fileId =
context.getProperty(FILE_ID).evaluateAttributeExpressions(flowFile).getValue();
+ final String templateKey =
context.getProperty(TEMPLATE_KEY).evaluateAttributeExpressions(flowFile).getValue();
+ final String templateScope =
context.getProperty(TEMPLATE_SCOPE).evaluateAttributeExpressions(flowFile).getValue();
try {
final BoxFile boxFile = getBoxFile(fileId);
+ final Metadata metadata = boxFile.getMetadata(templateKey,
templateScope);
- final List<Map<String, Object>> instanceList = 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.id", fileId);
- flowFile = session.putAttribute(flowFile,
"box.metadata.instances.count", "0");
- session.transfer(flowFile, REL_SUCCESS);
- return;
- }
-
- while (iterator.hasNext()) {
- final Metadata metadata = iterator.next();
- final Map<String, Object> instanceFields = new HashMap<>();
-
- templateNames.add(metadata.getTemplateName());
-
- // Add standard metadata fields
- instanceFields.put("$id", metadata.getID());
- instanceFields.put("$type", metadata.getTypeName());
- instanceFields.put("$parent", "file_" + fileId); // match the
Box API format
- instanceFields.put("$template", metadata.getTemplateName());
- instanceFields.put("$scope", metadata.getScope());
-
- for (final String fieldName : metadata.getPropertyPaths()) {
- final JsonValue jsonValue = metadata.getValue(fieldName);
- if (jsonValue != null) {
- final String cleanFieldName =
fieldName.startsWith("/") ? fieldName.substring(1) : fieldName;
- final Object fieldValue = parseJsonValue(jsonValue);
- instanceFields.put(cleanFieldName, fieldValue);
- }
- }
- instanceList.add(instanceFields);
- }
+ final Map<String, Object> instanceFields = new HashMap<>();
+ processBoxMetadataInstance(fileId, metadata, instanceFields);
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 : instanceList) {
- writer.write(templateFields);
- }
+ writer.write(instanceFields);
}
final Map<String, String> recordAttributes = new HashMap<>();
- recordAttributes.put("record.count",
String.valueOf(instanceList.size()));
recordAttributes.put(CoreAttributes.MIME_TYPE.key(),
"application/json");
recordAttributes.put("box.id", fileId);
- recordAttributes.put("box.metadata.instances.names",
String.join(",", templateNames));
- recordAttributes.put("box.metadata.instances.count",
String.valueOf(instanceList.size()));
+ recordAttributes.put("box.metadata.template.key", templateKey);
+ recordAttributes.put("box.metadata.template.scope",
templateScope);
flowFile = session.putAllAttributes(flowFile,
recordAttributes);
- session.getProvenanceReporter().receive(flowFile,
BoxFileUtils.BOX_URL + fileId + "/metadata");
+ session.getProvenanceReporter().receive(flowFile,
+ String.format("%s/%s/metadata/%s",
BoxFileUtils.BOX_URL, fileId, templateKey));
session.transfer(flowFile, REL_SUCCESS);
} catch (final IOException e) {
- getLogger().error("Failed writing metadata instances from file
[{}]", fileId, e);
+ getLogger().error("Failed writing metadata instance from file
[{}] with template key [{}] and scope [{}]",
+ fileId, templateKey, templateScope, e);
flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
session.transfer(flowFile, REL_FAILURE);
}
@@ -204,14 +191,22 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
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);
+ final String errorBody = e.getResponse();
+ if (errorBody != null &&
errorBody.toLowerCase().contains("instance_not_found")) {
+ getLogger().warn("Box metadata template with key {} and
scope {} was not found.", templateKey, templateScope);
+ session.transfer(flowFile, REL_TEMPLATE_NOT_FOUND);
+ } else {
+ getLogger().warn("Box file with ID {} was not found.",
fileId);
+ session.transfer(flowFile, REL_FILE_NOT_FOUND);
+ }
} else {
- getLogger().error("Couldn't fetch metadata instances from file
with id [{}]", fileId, e);
+ getLogger().error("Couldn't fetch metadata instance from file
with id [{}] with template key [{}] and scope [{}]",
+ fileId, templateKey, templateScope, e);
session.transfer(flowFile, REL_FAILURE);
}
} catch (final Exception e) {
- getLogger().error("Failed to process metadata instances for file
[{}]", fileId, e);
+ getLogger().error("Failed to process metadata instance for file
[{}] with template key [{}] and scope [{}]",
+ fileId, templateKey, templateScope, e);
flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
session.transfer(flowFile, REL_FAILURE);
}
@@ -226,42 +221,4 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
BoxFile getBoxFile(final String fileId) {
return new BoxFile(boxAPIConnection, fileId);
}
-
- /**
- * Parses a JsonValue and returns the appropriate Java object.
- * Box does not allow exponential notation in metadata values, so we need
to handle
- * special number formats. For numbers containing decimal points or
exponents, we try to
- * convert them to BigDecimal first for precise representation. If that
fails, we
- * fall back to double, which might lose precision but allows the
processing to continue.
- *
- * @param jsonValue The JsonValue to parse.
- * @return The parsed Java object.
- */
- protected static Object parseJsonValue(final JsonValue jsonValue) {
- if (jsonValue == null) {
- return null;
- }
- if (jsonValue.isString()) {
- return jsonValue.asString();
- } else if (jsonValue.isNumber()) {
- final String numberString = jsonValue.toString();
- if (numberString.contains(".") ||
numberString.toLowerCase().contains("e")) {
- try {
- return (new BigDecimal(numberString)).toPlainString();
- } catch (final NumberFormatException e) {
- return jsonValue.asDouble();
- }
- } else {
- try {
- return jsonValue.asLong();
- } catch (final NumberFormatException e) {
- return (new BigDecimal(numberString)).toPlainString();
- }
- }
- } else if (jsonValue.isBoolean()) {
- return jsonValue.asBoolean();
- }
- // Fallback: return the string representation.
- return jsonValue.toString();
- }
}
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
index 3c05d841a1..ea95e2af78 100644
---
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstances.java
@@ -20,7 +20,6 @@ import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxAPIResponseException;
import com.box.sdk.BoxFile;
import com.box.sdk.Metadata;
-import com.eclipsesource.json.JsonValue;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
@@ -42,7 +41,6 @@ import org.apache.nifi.processor.util.StandardValidators;
import java.io.IOException;
import java.io.OutputStream;
-import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -56,6 +54,7 @@ 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;
+import static
org.apache.nifi.processors.box.utils.BoxMetadataUtils.processBoxMetadataInstance;
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
@Tags({"box", "storage", "metadata", "instances", "templates"})
@@ -157,20 +156,7 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
templateNames.add(metadata.getTemplateName());
// Add standard metadata fields
- instanceFields.put("$id", metadata.getID());
- instanceFields.put("$type", metadata.getTypeName());
- instanceFields.put("$parent", "file_" + fileId); // match the
Box API format
- instanceFields.put("$template", metadata.getTemplateName());
- instanceFields.put("$scope", metadata.getScope());
-
- for (final String fieldName : metadata.getPropertyPaths()) {
- final JsonValue jsonValue = metadata.getValue(fieldName);
- if (jsonValue != null) {
- final String cleanFieldName =
fieldName.startsWith("/") ? fieldName.substring(1) : fieldName;
- final Object fieldValue = parseJsonValue(jsonValue);
- instanceFields.put(cleanFieldName, fieldValue);
- }
- }
+ processBoxMetadataInstance(fileId, metadata, instanceFields);
instanceList.add(instanceFields);
}
@@ -226,42 +212,4 @@ public class ListBoxFileMetadataInstances extends
AbstractProcessor {
BoxFile getBoxFile(final String fileId) {
return new BoxFile(boxAPIConnection, fileId);
}
-
- /**
- * Parses a JsonValue and returns the appropriate Java object.
- * Box does not allow exponential notation in metadata values, so we need
to handle
- * special number formats. For numbers containing decimal points or
exponents, we try to
- * convert them to BigDecimal first for precise representation. If that
fails, we
- * fall back to double, which might lose precision but allows the
processing to continue.
- *
- * @param jsonValue The JsonValue to parse.
- * @return The parsed Java object.
- */
- protected static Object parseJsonValue(final JsonValue jsonValue) {
- if (jsonValue == null) {
- return null;
- }
- if (jsonValue.isString()) {
- return jsonValue.asString();
- } else if (jsonValue.isNumber()) {
- final String numberString = jsonValue.toString();
- if (numberString.contains(".") ||
numberString.toLowerCase().contains("e")) {
- try {
- return (new BigDecimal(numberString)).toPlainString();
- } catch (final NumberFormatException e) {
- return jsonValue.asDouble();
- }
- } else {
- try {
- return jsonValue.asLong();
- } catch (final NumberFormatException e) {
- return (new BigDecimal(numberString)).toPlainString();
- }
- }
- } else if (jsonValue.isBoolean()) {
- return jsonValue.asBoolean();
- }
- // Fallback: return the string representation.
- return jsonValue.toString();
- }
}
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/utils/BoxMetadataUtils.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/utils/BoxMetadataUtils.java
new file mode 100644
index 0000000000..596e018d9a
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/utils/BoxMetadataUtils.java
@@ -0,0 +1,90 @@
+/*
+ * 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.utils;
+
+import com.box.sdk.Metadata;
+import com.eclipsesource.json.JsonValue;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public class BoxMetadataUtils {
+
+ /**
+ * Parses a JsonValue and returns the appropriate Java object.
+ * Box does not allow exponential notation in metadata values, so we need
to handle
+ * special number formats. For numbers containing decimal points or
exponents, we try to
+ * convert them to BigDecimal first for precise representation. If that
fails, we
+ * fall back to double, which might lose precision but allows the
processing to continue.
+ *
+ * @param jsonValue The JsonValue to parse.
+ * @return The parsed Java object.
+ */
+ public static Object parseJsonValue(final JsonValue jsonValue) {
+ if (jsonValue == null) {
+ return null;
+ }
+ if (jsonValue.isString()) {
+ return jsonValue.asString();
+ } else if (jsonValue.isNumber()) {
+ final String numberString = jsonValue.toString();
+ if (numberString.contains(".") ||
numberString.toLowerCase().contains("e")) {
+ try {
+ return (new BigDecimal(numberString)).toPlainString();
+ } catch (final NumberFormatException e) {
+ return jsonValue.asDouble();
+ }
+ } else {
+ try {
+ return jsonValue.asLong();
+ } catch (final NumberFormatException e) {
+ return (new BigDecimal(numberString)).toPlainString();
+ }
+ }
+ } else if (jsonValue.isBoolean()) {
+ return jsonValue.asBoolean();
+ }
+ // Fallback: return the string representation.
+ return jsonValue.toString();
+ }
+
+ /**
+ * Processes Box metadata instance and populates the provided map with the
default fields.
+ *
+ * @param fileId The ID of the file.
+ * @param metadata The Box metadata instance.
+ * @param instanceFields The map to populate with metadata fields.
+ */
+ public static void processBoxMetadataInstance(final String fileId,
+ final Metadata metadata,
+ final Map<String, Object>
instanceFields) {
+ instanceFields.put("$id", metadata.getID());
+ instanceFields.put("$type", metadata.getTypeName());
+ instanceFields.put("$parent", "file_" + fileId); // match the Box API
format
+ instanceFields.put("$template", metadata.getTemplateName());
+ instanceFields.put("$scope", metadata.getScope());
+
+ for (final String fieldName : metadata.getPropertyPaths()) {
+ final JsonValue jsonValue = metadata.getValue(fieldName);
+ if (jsonValue != null) {
+ final String cleanFieldName = fieldName.startsWith("/") ?
fieldName.substring(1) : fieldName;
+ final Object fieldValue = parseJsonValue(jsonValue);
+ instanceFields.put(cleanFieldName, fieldValue);
+ }
+ }
+ }
+}
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 74ed0b7e9c..90461c91e2 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
@@ -20,6 +20,7 @@ org.apache.nifi.processors.box.CreateBoxFileMetadataInstance
org.apache.nifi.processors.box.ExtractStructuredBoxFileMetadata
org.apache.nifi.processors.box.FetchBoxFile
org.apache.nifi.processors.box.FetchBoxFileInfo
+org.apache.nifi.processors.box.FetchBoxFileMetadataInstance
org.apache.nifi.processors.box.FetchBoxFileRepresentation
org.apache.nifi.processors.box.GetBoxFileCollaborators
org.apache.nifi.processors.box.GetBoxGroupMembers
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstancesParseJsonTest.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxParseJsonTest.java
similarity index 72%
rename from
nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstancesParseJsonTest.java
rename to
nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxParseJsonTest.java
index 521d664984..c6279f8c95 100644
---
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileMetadataInstancesParseJsonTest.java
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/BoxParseJsonTest.java
@@ -20,21 +20,22 @@ import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
+import org.apache.nifi.processors.box.utils.BoxMetadataUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-public class ListBoxFileMetadataInstancesParseJsonTest {
+public class BoxParseJsonTest {
@Test
void testParseString() {
String expected = "test string";
- Object result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ Object result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
// Empty string
expected = "";
- result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
}
@@ -42,12 +43,12 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
void testParseBoolean() {
// Test true
boolean expected = true;
- Object result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ Object result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
// Test false
expected = false;
- result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
}
@@ -55,17 +56,17 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
void testParseIntegerNumber() {
// Integer value
long expected = 42;
- Object result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ Object result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
// Max long value
expected = Long.MAX_VALUE;
- result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
// Min long value
expected = Long.MIN_VALUE;
- result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(expected));
+ result = BoxMetadataUtils.parseJsonValue(Json.value(expected));
assertEquals(expected, result);
}
@@ -74,19 +75,19 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
// Double without exponent
String input = "3.14159";
JsonValue jsonValue = Json.parse(input);
- Object result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ Object result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals(input, result);
// Very small number that should be preserved
input = "0.0000000001";
jsonValue = Json.parse(input);
- result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals(input, result);
// Very large number that should be preserved
input = "9999999999999999.9999";
jsonValue = Json.parse(input);
- result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals(input, result);
}
@@ -95,19 +96,19 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
// Scientific notation is converted to plain string format
String input = "1.234e5";
JsonValue jsonValue = Json.parse(input);
- Object result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ Object result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals("123400", result);
// large exponent
input = "1.234e20";
jsonValue = Json.parse(input);
- result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals("123400000000000000000", result);
// Negative exponent
input = "1.234e-5";
jsonValue = Json.parse(input);
- result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals("0.00001234", result);
}
@@ -115,12 +116,12 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
void testParseObjectAndArray() {
// JSON objects return their string representation
JsonObject jsonObject = Json.object().add("key", "value");
- Object result =
ListBoxFileMetadataInstances.parseJsonValue(jsonObject);
+ Object result = BoxMetadataUtils.parseJsonValue(jsonObject);
assertEquals(jsonObject.toString(), result);
// JSON arrays return their string representation
JsonArray jsonArray = Json.array().add("item1").add("item2");
- result = ListBoxFileMetadataInstances.parseJsonValue(jsonArray);
+ result = BoxMetadataUtils.parseJsonValue(jsonArray);
assertEquals(jsonArray.toString(), result);
}
@@ -128,11 +129,11 @@ public class ListBoxFileMetadataInstancesParseJsonTest {
void testParseNumberFormatException() {
String largeIntegerString = "9999999999999999999"; // Beyond
Long.MAX_VALUE
JsonValue jsonValue = Json.parse(largeIntegerString);
- Object result = ListBoxFileMetadataInstances.parseJsonValue(jsonValue);
+ Object result = BoxMetadataUtils.parseJsonValue(jsonValue);
assertEquals(largeIntegerString, result);
double doubleValue = 123.456;
- result =
ListBoxFileMetadataInstances.parseJsonValue(Json.value(doubleValue));
+ result = BoxMetadataUtils.parseJsonValue(Json.value(doubleValue));
assertEquals(String.valueOf(doubleValue), result);
}
}
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstanceTest.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstanceTest.java
new file mode 100644
index 0000000000..ec12f25385
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileMetadataInstanceTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.Json;
+import com.eclipsesource.json.JsonObject;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+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 static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class FetchBoxFileMetadataInstanceTest extends AbstractBoxFileTest {
+
+ private static final String TEMPLATE_KEY = "fileMetadata";
+ private static final String TEMPLATE_SCOPE = "enterprise_123";
+ private static final String TEMPLATE_ID = "12345";
+
+ @Mock
+ private BoxFile mockBoxFile;
+
+ @Override
+ @BeforeEach
+ void setUp() throws Exception {
+ final FetchBoxFileMetadataInstance testSubject = new
FetchBoxFileMetadataInstance() {
+ @Override
+ BoxFile getBoxFile(String fileId) {
+ return mockBoxFile;
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+ super.setUp();
+ }
+
+ @Test
+ void testSuccessfulMetadataRetrieval() {
+ final JsonObject metadataJson = Json.object()
+ .add("$id", TEMPLATE_ID)
+ .add("$type", "fileMetadata-123")
+ .add("$parent", "file_" + TEST_FILE_ID)
+ .add("$template", TEMPLATE_KEY)
+ .add("$scope", TEMPLATE_SCOPE)
+ .add("fileName", "document.pdf")
+ .add("fileExtension", "pdf");
+ final Metadata metadata = new Metadata(metadataJson);
+
+ when(mockBoxFile.getMetadata(TEMPLATE_KEY,
TEMPLATE_SCOPE)).thenReturn(metadata);
+
+ testRunner.setProperty(FetchBoxFileMetadataInstance.FILE_ID,
TEST_FILE_ID);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_KEY,
TEMPLATE_KEY);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_SCOPE,
TEMPLATE_SCOPE);
+ testRunner.enqueue(new byte[0]);
+ testRunner.run();
+
testRunner.assertAllFlowFilesTransferred(FetchBoxFileMetadataInstance.REL_SUCCESS,
1);
+
+ final MockFlowFile flowFile =
testRunner.getFlowFilesForRelationship(FetchBoxFileMetadataInstance.REL_SUCCESS).getFirst();
+ flowFile.assertAttributeEquals("box.id", TEST_FILE_ID);
+ flowFile.assertAttributeEquals(CoreAttributes.MIME_TYPE.key(),
"application/json");
+ flowFile.assertAttributeEquals("box.metadata.template.key",
TEMPLATE_KEY);
+ flowFile.assertAttributeEquals("box.metadata.template.scope",
TEMPLATE_SCOPE);
+
+ final String content = new String(flowFile.toByteArray());
+ assertTrue(content.contains("\"$id\":\"" + TEMPLATE_ID + "\""));
+ assertTrue(content.contains("\"$template\":\"" + TEMPLATE_KEY + "\""));
+ assertTrue(content.contains("\"$scope\":\"" + TEMPLATE_SCOPE + "\""));
+ assertTrue(content.contains("\"$parent\":\"file_" + TEST_FILE_ID +
"\""));
+ assertTrue(content.contains("\"fileName\":\"document.pdf\""));
+ assertTrue(content.contains("\"fileExtension\":\"pdf\""));
+ }
+
+ @Test
+ void testMetadataNotFound() {
+ when(mockBoxFile.getMetadata(anyString(), anyString())).thenThrow(
+ new BoxAPIResponseException("instance_not_found - Template not
found", 404, "instance_not_found", null));
+
+ testRunner.setProperty(FetchBoxFileMetadataInstance.FILE_ID,
TEST_FILE_ID);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_KEY,
TEMPLATE_KEY);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_SCOPE,
TEMPLATE_SCOPE);
+ testRunner.enqueue(new byte[0]);
+ testRunner.run();
+
+
testRunner.assertAllFlowFilesTransferred(FetchBoxFileMetadataInstance.REL_TEMPLATE_NOT_FOUND,
1);
+ final MockFlowFile flowFile =
testRunner.getFlowFilesForRelationship(FetchBoxFileMetadataInstance.REL_TEMPLATE_NOT_FOUND).getFirst();
+ flowFile.assertAttributeExists(BoxFileAttributes.ERROR_MESSAGE);
+ }
+
+ @Test
+ void testFileNotFound() {
+ final BoxAPIResponseException mockException = new
BoxAPIResponseException("API Error", 404, "Box File Not Found", null);
+ doThrow(mockException).when(mockBoxFile).getMetadata(anyString(),
anyString());
+
+ testRunner.setProperty(FetchBoxFileMetadataInstance.FILE_ID,
TEST_FILE_ID);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_KEY,
TEMPLATE_KEY);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_SCOPE,
TEMPLATE_SCOPE);
+ testRunner.enqueue(new byte[0]);
+ testRunner.run();
+
+
testRunner.assertAllFlowFilesTransferred(FetchBoxFileMetadataInstance.REL_FILE_NOT_FOUND,
1);
+ final MockFlowFile flowFile =
testRunner.getFlowFilesForRelationship(FetchBoxFileMetadataInstance.REL_FILE_NOT_FOUND).getFirst();
+ flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_CODE, "404");
+ flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE, "API
Error [404]");
+ }
+
+ @Test
+ void testBoxApiException() {
+ final BoxAPIException mockException = new BoxAPIException("General API
Error", 500, "Unexpected Error");
+ doThrow(mockException).when(mockBoxFile).getMetadata(anyString(),
anyString());
+
+ testRunner.setProperty(FetchBoxFileMetadataInstance.FILE_ID,
TEST_FILE_ID);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_KEY,
TEMPLATE_KEY);
+ testRunner.setProperty(FetchBoxFileMetadataInstance.TEMPLATE_SCOPE,
TEMPLATE_SCOPE);
+ testRunner.enqueue(new byte[0]);
+ testRunner.run();
+
+
testRunner.assertAllFlowFilesTransferred(FetchBoxFileMetadataInstance.REL_FAILURE,
1);
+ final MockFlowFile flowFile =
testRunner.getFlowFilesForRelationship(FetchBoxFileMetadataInstance.REL_FAILURE).getFirst();
+ flowFile.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE,
"General API Error\nUnexpected Error");
+ }
+}