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 1e0b195c0c NIFI-14329: Creating processor to fetch Box File Metadata
1e0b195c0c is described below
commit 1e0b195c0c4d4d6fefe270549018e2ac1eb1c380
Author: Noah Cover <[email protected]>
AuthorDate: Wed Mar 5 09:08:10 2025 -0800
NIFI-14329: Creating processor to fetch Box File Metadata
Signed-off-by: Pierre Villard <[email protected]>
This closes #9772.
---
.../nifi/processors/box/FetchBoxFileInfo.java | 228 +++++++++++++++++++++
.../services/org.apache.nifi.processor.Processor | 1 +
.../nifi/processors/box/FetchBoxFileInfoTest.java | 198 ++++++++++++++++++
3 files changed, 427 insertions(+)
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileInfo.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileInfo.java
new file mode 100644
index 0000000000..f588042ff3
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFileInfo.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.BoxAPIException;
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFile;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.ReadsAttribute;
+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.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 org.jetbrains.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.BoxFileAttributes.FILENAME_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ID;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ID_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.PATH_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.SIZE;
+import static org.apache.nifi.processors.box.BoxFileAttributes.SIZE_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.TIMESTAMP;
+import static org.apache.nifi.processors.box.BoxFileAttributes.TIMESTAMP_DESC;
+
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"box", "storage", "metadata", "fetch"})
+@CapabilityDescription("Fetches metadata for files from Box and adds it to the
FlowFile's attributes.")
+@SeeAlso({ListBoxFile.class, FetchBoxFile.class, PutBoxFile.class})
+@ReadsAttribute(attribute = ID, description = ID_DESC)
+@WritesAttributes({
+ @WritesAttribute(attribute = ID, description = ID_DESC),
+ @WritesAttribute(attribute = "filename", description = FILENAME_DESC),
+ @WritesAttribute(attribute = "path", description = PATH_DESC),
+ @WritesAttribute(attribute = SIZE, description = SIZE_DESC),
+ @WritesAttribute(attribute = TIMESTAMP, description = TIMESTAMP_DESC),
+ @WritesAttribute(attribute = "box.created.at", description = "The
creation date of the file"),
+ @WritesAttribute(attribute = "box.owner", description = "The owner of
the file"),
+ @WritesAttribute(attribute = "box.description", description = "The
description of the file"),
+ @WritesAttribute(attribute = "box.etag", description = "The etag of
the file"),
+ @WritesAttribute(attribute = "box.sha1", description = "The SHA-1 hash
of the file"),
+ @WritesAttribute(attribute = "box.content.created.at", description =
"The date the content was created"),
+ @WritesAttribute(attribute = "box.content.modified.at", description =
"The date the content was modified"),
+ @WritesAttribute(attribute = "box.item.status", description = "The
status of the file (active, trashed, etc.)"),
+ @WritesAttribute(attribute = "box.sequence_id", description = "The
sequence ID of the file"),
+ @WritesAttribute(attribute = "box.parent.folder.id", description =
"The ID of the parent folder"),
+ @WritesAttribute(attribute = "box.trashed.at", description = "The date
the file was trashed, if applicable"),
+ @WritesAttribute(attribute = "box.purged.at", description = "The date
the file was purged, if applicable"),
+ @WritesAttribute(attribute = "box.shared.link", description = "The
shared link of the file, if any"),
+ @WritesAttribute(attribute = ERROR_CODE, description =
ERROR_CODE_DESC),
+ @WritesAttribute(attribute = ERROR_MESSAGE, description =
ERROR_MESSAGE_DESC)
+})
+public class FetchBoxFileInfo extends AbstractProcessor {
+
+ public static final PropertyDescriptor FILE_ID = new
PropertyDescriptor.Builder()
+ .name("File ID")
+ .description("The ID of the File to fetch metadata for")
+ .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 will be routed here after successfully
fetching the file metadata.")
+ .build();
+
+ public static final Relationship REL_FAILURE = new Relationship.Builder()
+ .name("failure")
+ .description("A FlowFile will be routed here if fetching the file
metadata fails.")
+ .build();
+
+ static final Relationship REL_NOT_FOUND = new Relationship.Builder()
+ .name("not.found")
+ .description("FlowFiles for which the specified Box file was not
found.")
+ .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) {
+ 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 {
+ flowFile = fetchFileMetadata(fileId, session, flowFile);
+ session.transfer(flowFile, REL_SUCCESS);
+ } catch (final BoxAPIResponseException e) {
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.putAttribute(flowFile, ERROR_CODE,
String.valueOf(e.getResponseCode()));
+
+ if (e.getResponseCode() == 404) {
+ getLogger().warn("Box file with ID {} was not found.", fileId);
+ session.transfer(flowFile, REL_NOT_FOUND);
+ } else {
+ getLogger().error("Failed to retrieve Box file representation
for file [{}]", fileId, e);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+ } catch (final BoxAPIException e) {
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.putAttribute(flowFile, ERROR_CODE,
String.valueOf(e.getResponseCode()));
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+ }
+
+ /**
+ * Fetches the BoxFile instance for a given file ID. For testing purposes.
+ *
+ * @param fileId the ID of the file
+ * @return BoxFile instance
+ */
+ @VisibleForTesting
+ protected BoxFile getBoxFile(final String fileId) {
+ return new BoxFile(boxAPIConnection, fileId);
+ }
+
+ private FlowFile fetchFileMetadata(final String fileId,
+ final ProcessSession session,
+ final FlowFile flowFile) {
+ final BoxFile boxFile = getBoxFile(fileId);
+ final BoxFile.Info fileInfo = boxFile.getInfo("name", "description",
"size", "created_at", "modified_at",
+ "owned_by", "parent", "etag", "sha1", "item_status",
"sequence_id", "path_collection",
+ "content_created_at", "content_modified_at", "trashed_at",
"purged_at", "shared_link");
+
+ final Map<String, String> attributes = new
HashMap<>(BoxFileUtils.createAttributeMap(fileInfo));
+
+ addAttributeIfNotNull(attributes, "box.description",
fileInfo.getDescription());
+ addAttributeIfNotNull(attributes, "box.etag", fileInfo.getEtag());
+ addAttributeIfNotNull(attributes, "box.sha1", fileInfo.getSha1());
+ addAttributeIfNotNull(attributes, "box.content.created.at",
fileInfo.getContentCreatedAt());
+ addAttributeIfNotNull(attributes, "box.content.modified.at",
fileInfo.getContentModifiedAt());
+ addAttributeIfNotNull(attributes, "box.item.status",
fileInfo.getItemStatus());
+ addAttributeIfNotNull(attributes, "box.sequence.id",
fileInfo.getSequenceID());
+ addAttributeIfNotNull(attributes, "box.created.at",
fileInfo.getCreatedAt());
+ addAttributeIfNotNull(attributes, "box.trashed.at",
fileInfo.getTrashedAt());
+ addAttributeIfNotNull(attributes, "box.purged.at",
fileInfo.getPurgedAt());
+
+ // Handle special cases
+ if (fileInfo.getOwnedBy() != null && fileInfo.getOwnedBy().getName()
!= null) {
+ attributes.put("box.owner", fileInfo.getOwnedBy().getName());
+ }
+
+ if (fileInfo.getParent() != null) {
+ attributes.put("box.parent.folder.id",
fileInfo.getParent().getID());
+ }
+
+ if (fileInfo.getSharedLink() != null &&
fileInfo.getSharedLink().getURL() != null) {
+ attributes.put("box.shared.link",
fileInfo.getSharedLink().getURL());
+ }
+
+ return session.putAllAttributes(flowFile, attributes);
+ }
+
+ private void addAttributeIfNotNull(final Map<String, String> attributes,
+ final String key,
+ final Object value) {
+ if (value != null) {
+ attributes.put(key, String.valueOf(value));
+ }
+ }
+}
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 0fe58b2359..2a4163074e 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
@@ -14,5 +14,6 @@
# limitations under the License.
org.apache.nifi.processors.box.ListBoxFile
org.apache.nifi.processors.box.FetchBoxFile
+org.apache.nifi.processors.box.FetchBoxFileInfo
org.apache.nifi.processors.box.PutBoxFile
org.apache.nifi.processors.box.ConsumeBoxEvents
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileInfoTest.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileInfoTest.java
new file mode 100644
index 0000000000..ebdb4e20a0
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileInfoTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.BoxSharedLink;
+import com.box.sdk.BoxUser;
+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.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class FetchBoxFileInfoTest extends AbstractBoxFileTest {
+ private static final String TEST_DESCRIPTION = "Test file description";
+ private static final String TEST_ETAG = "0";
+ private static final String TEST_SHA1 =
"da39a3ee5e6b4b0d3255bfef95601890afd80709";
+ private static final String TEST_ITEM_STATUS = "active";
+ private static final String TEST_SEQUENCE_ID = "1";
+ private static final String TEST_OWNER_NAME = "Test User";
+ private static final String TEST_SHARED_LINK_URL =
"https://app.box.com/s/abcdef123456";
+ private static final Date TEST_CREATED_AT = new Date(12345678L);
+ private static final Date TEST_CONTENT_CREATED_AT = new Date(12345600L);
+ private static final Date TEST_CONTENT_MODIFIED_AT = new Date(12345700L);
+ private static final Date TEST_TRASHED_AT = null;
+ private static final Date TEST_PURGED_AT = null;
+
+ @Mock
+ BoxFile mockBoxFile;
+
+ @Mock
+ BoxUser.Info mockBoxUser;
+
+ @Mock
+ BoxSharedLink mockSharedLink;
+
+ @Override
+ @BeforeEach
+ void setUp() throws Exception {
+ final FetchBoxFileInfo testSubject = new FetchBoxFileInfo() {
+ @Override
+ protected BoxFile getBoxFile(String fileId) {
+ return mockBoxFile;
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+ super.setUp();
+ }
+
+ @Test
+ void testFetchMetadataFromFlowFileAttribute() {
+ testRunner.setProperty(FetchBoxFileInfo.FILE_ID, "${box.id}");
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put(BoxFileAttributes.ID, TEST_FILE_ID);
+ inputFlowFile.putAttributes(attributes);
+
+ setupMockFileInfoWithExtendedAttributes();
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFileInfo.REL_SUCCESS,
1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFileInfo.REL_SUCCESS);
+ final MockFlowFile flowFilesFirst = flowFiles.getFirst();
+
+ assertOutFlowFileAttributes(flowFilesFirst);
+ verifyExtendedAttributes(flowFilesFirst);
+ }
+
+ @Test
+ void testFetchMetadataFromProperty() {
+ testRunner.setProperty(FetchBoxFileInfo.FILE_ID, TEST_FILE_ID);
+
+ setupMockFileInfoWithExtendedAttributes();
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFileInfo.REL_SUCCESS,
1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFileInfo.REL_SUCCESS);
+ final MockFlowFile flowFilesFirst = flowFiles.getFirst();
+
+ assertOutFlowFileAttributes(flowFilesFirst);
+ verifyExtendedAttributes(flowFilesFirst);
+ }
+
+ @Test
+ void testApiErrorHandling() {
+ testRunner.setProperty(FetchBoxFileInfo.FILE_ID, TEST_FILE_ID);
+
+ BoxAPIResponseException mockException = new
BoxAPIResponseException("API Error", 404, "Box File Not Found", null);
+ doThrow(mockException).when(mockBoxFile).getInfo(anyString(),
anyString(), anyString(), anyString(), anyString(),
+ anyString(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyString(),
+ anyString(), anyString(), anyString(), anyString(),
anyString());
+
+ MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+
testRunner.assertAllFlowFilesTransferred(FetchBoxFileInfo.REL_NOT_FOUND, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFileInfo.REL_NOT_FOUND);
+ final MockFlowFile flowFilesFirst = flowFiles.getFirst();
+ flowFilesFirst.assertAttributeEquals(BoxFileAttributes.ERROR_CODE,
"404");
+ flowFilesFirst.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE,
"API Error [404]");
+ }
+
+ @Test
+ void testBoxApiExceptionHandling() {
+ testRunner.setProperty(FetchBoxFileInfo.FILE_ID, TEST_FILE_ID);
+
+ BoxAPIException mockException = new BoxAPIException("General API
Error:", 500, "Unexpected Error");
+ doThrow(mockException).when(mockBoxFile).getInfo(anyString(),
anyString(), anyString(), anyString(), anyString(),
+ anyString(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyString(),
+ anyString(), anyString(), anyString(), anyString(),
anyString());
+
+ MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFileInfo.REL_FAILURE,
1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFileInfo.REL_FAILURE);
+ final MockFlowFile flowFilesFirst = flowFiles.getFirst();
+
+ flowFilesFirst.assertAttributeEquals(BoxFileAttributes.ERROR_CODE,
"500");
+ flowFilesFirst.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE,
"General API Error:\nUnexpected Error");
+ }
+
+ private void setupMockFileInfoWithExtendedAttributes() {
+ final BoxFile.Info fetchedFileInfo = createFileInfo(TEST_FOLDER_NAME,
MODIFIED_TIME);
+
+ // Set up additional metadata attributes
+ when(mockFileInfo.getDescription()).thenReturn(TEST_DESCRIPTION);
+ when(mockFileInfo.getEtag()).thenReturn(TEST_ETAG);
+ when(mockFileInfo.getSha1()).thenReturn(TEST_SHA1);
+ when(mockFileInfo.getItemStatus()).thenReturn(TEST_ITEM_STATUS);
+ when(mockFileInfo.getSequenceID()).thenReturn(TEST_SEQUENCE_ID);
+ when(mockFileInfo.getCreatedAt()).thenReturn(TEST_CREATED_AT);
+
when(mockFileInfo.getContentCreatedAt()).thenReturn(TEST_CONTENT_CREATED_AT);
+
when(mockFileInfo.getContentModifiedAt()).thenReturn(TEST_CONTENT_MODIFIED_AT);
+ when(mockFileInfo.getTrashedAt()).thenReturn(TEST_TRASHED_AT);
+ when(mockFileInfo.getPurgedAt()).thenReturn(TEST_PURGED_AT);
+
+ when(mockBoxUser.getName()).thenReturn(TEST_OWNER_NAME);
+ when(mockFileInfo.getOwnedBy()).thenReturn(mockBoxUser);
+ when(mockSharedLink.getURL()).thenReturn(TEST_SHARED_LINK_URL);
+ when(mockFileInfo.getSharedLink()).thenReturn(mockSharedLink);
+
+ // Return the file info when requested
+ doReturn(fetchedFileInfo).when(mockBoxFile).getInfo("name",
"description", "size", "created_at", "modified_at",
+ "owned_by", "parent", "etag", "sha1", "item_status",
"sequence_id", "path_collection",
+ "content_created_at", "content_modified_at", "trashed_at",
"purged_at", "shared_link");
+ }
+
+ private void verifyExtendedAttributes(MockFlowFile flowFile) {
+ flowFile.assertAttributeEquals("box.description", TEST_DESCRIPTION);
+ flowFile.assertAttributeEquals("box.etag", TEST_ETAG);
+ flowFile.assertAttributeEquals("box.sha1", TEST_SHA1);
+ flowFile.assertAttributeEquals("box.item.status", TEST_ITEM_STATUS);
+ flowFile.assertAttributeEquals("box.sequence.id", TEST_SEQUENCE_ID);
+ flowFile.assertAttributeEquals("box.created.at",
TEST_CREATED_AT.toString());
+ flowFile.assertAttributeEquals("box.content.created.at",
TEST_CONTENT_CREATED_AT.toString());
+ flowFile.assertAttributeEquals("box.content.modified.at",
TEST_CONTENT_MODIFIED_AT.toString());
+ flowFile.assertAttributeEquals("box.owner", TEST_OWNER_NAME);
+ flowFile.assertAttributeEquals("box.shared.link",
TEST_SHARED_LINK_URL);
+ }
+}
\ No newline at end of file