This is an automated email from the ASF dual-hosted git repository.
turcsanyi 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 653631cc67 NIFI-11059 PutBoxFile processor
653631cc67 is described below
commit 653631cc675d8067a6e4ac4d5f66eb9ecf2cb33c
Author: krisztina-zsihovszki <[email protected]>
AuthorDate: Tue Jan 17 09:24:17 2023 +0100
NIFI-11059 PutBoxFile processor
This closes #6892.
Signed-off-by: Peter Turcsanyi <[email protected]>
---
.../nifi-box-bundle/nifi-box-processors/pom.xml | 5 +
.../nifi/processors/box/BoxFileAttributes.java | 40 ++
.../apache/nifi/processors/box/BoxFileInfo.java | 18 +-
.../apache/nifi/processors/box/BoxFileUtils.java | 55 +++
.../nifi/processors/box/BoxFlowFileAttribute.java | 11 +-
.../apache/nifi/processors/box/FetchBoxFile.java | 121 +++---
.../apache/nifi/processors/box/ListBoxFile.java | 56 +--
.../org/apache/nifi/processors/box/PutBoxFile.java | 429 +++++++++++++++++++++
.../services/org.apache.nifi.processor.Processor | 1 +
.../additionalDetails.html | 46 +++
.../additionalDetails.html | 43 +++
.../additionalDetails.html | 42 ++
.../nifi/processors/box/AbstractBoxFileIT.java | 32 +-
.../nifi/processors/box/AbstractBoxFileTest.java | 125 ++++++
.../apache/nifi/processors/box/FetchBoxFileIT.java | 55 ++-
.../nifi/processors/box/FetchBoxFileTest.java | 120 ++++++
...ileTestTrait.java => FileListingTestTrait.java} | 31 +-
.../apache/nifi/processors/box/ListBoxFileIT.java | 13 -
...SimpleTest.java => ListBoxFileListingTest.java} | 48 ++-
.../nifi/processors/box/ListBoxFileTest.java | 110 ++++++
.../processors/box/ListBoxFileTestRunnerTest.java | 180 ---------
.../apache/nifi/processors/box/PutBoxFileIT.java | 120 ++++++
.../apache/nifi/processors/box/PutBoxFileTest.java | 212 ++++++++++
23 files changed, 1543 insertions(+), 370 deletions(-)
diff --git a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
index 93ddaeff53..399c265e46 100644
--- a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
+++ b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
@@ -41,6 +41,11 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-record</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-conflict-resolution</artifactId>
+ <version>1.20.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileAttributes.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileAttributes.java
new file mode 100644
index 0000000000..4f608da3a1
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileAttributes.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+public class BoxFileAttributes {
+
+ public static final String ID = "box.id";
+ public static final String ID_DESC = "The id of the file";
+
+ public static final String FILENAME_DESC = "The name of the file";
+
+ public static final String PATH_DESC = "The folder path where the file is
located";
+
+ public static final String SIZE = "box.size";
+ public static final String SIZE_DESC = "The size of the file";
+
+ public static final String TIMESTAMP = "box.timestamp";
+ public static final String TIMESTAMP_DESC = "The last modified time of
the file";
+
+ public static final String ERROR_MESSAGE = "error.message";
+ public static final String ERROR_MESSAGE_DESC = "The error message
returned by Box";
+
+ public static final String ERROR_CODE = "error.code";
+ public static final String ERROR_CODE_DESC = "The error code returned by
Box";
+
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileInfo.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileInfo.java
index 38cddb65f0..a8b850ed05 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileInfo.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileInfo.java
@@ -16,6 +16,11 @@
*/
package org.apache.nifi.processors.box;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ID;
+import static org.apache.nifi.processors.box.BoxFileAttributes.SIZE;
+import static org.apache.nifi.processors.box.BoxFileAttributes.TIMESTAMP;
+
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.processor.util.list.ListableEntity;
import org.apache.nifi.serialization.SimpleRecordSchema;
import org.apache.nifi.serialization.record.MapRecord;
@@ -30,11 +35,6 @@ import java.util.List;
import java.util.Map;
public class BoxFileInfo implements ListableEntity {
- public static final String ID = "box.id";
- public static final String FILENAME = "filename";
- public static final String PATH = "path";
- public static final String SIZE = "box.size";
- public static final String TIMESTAMP = "box.timestamp";
private static final RecordSchema SCHEMA;
@@ -42,8 +42,8 @@ public class BoxFileInfo implements ListableEntity {
final List<RecordField> recordFields = new ArrayList<>();
recordFields.add(new RecordField(ID,
RecordFieldType.STRING.getDataType(), false));
- recordFields.add(new RecordField(FILENAME,
RecordFieldType.STRING.getDataType(), false));
- recordFields.add(new RecordField(PATH,
RecordFieldType.STRING.getDataType(), false));
+ recordFields.add(new RecordField(CoreAttributes.FILENAME.key(),
RecordFieldType.STRING.getDataType(), false));
+ recordFields.add(new RecordField(CoreAttributes.PATH.key(),
RecordFieldType.STRING.getDataType(), false));
recordFields.add(new RecordField(SIZE,
RecordFieldType.LONG.getDataType(), false));
recordFields.add(new RecordField(TIMESTAMP,
RecordFieldType.LONG.getDataType(), false));
@@ -82,8 +82,8 @@ public class BoxFileInfo implements ListableEntity {
final Map<String, Object> values = new HashMap<>();
values.put(ID, getId());
- values.put(FILENAME, getName());
- values.put(PATH, getPath());
+ values.put(CoreAttributes.FILENAME.key(), getName());
+ values.put(CoreAttributes.PATH.key(), getPath());
values.put(SIZE, getSize());
values.put(TIMESTAMP, getTimestamp());
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileUtils.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileUtils.java
new file mode 100644
index 0000000000..81d72172f9
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFileUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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 static java.lang.String.valueOf;
+
+import com.box.sdk.BoxFile;
+import com.box.sdk.BoxFolder;
+import com.box.sdk.BoxItem;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+
+public final class BoxFileUtils {
+
+ public static final String BOX_URL = "https://app.box.com/file/";
+
+ public static String getParentPath(BoxItem.Info info) {
+ return "/" + info.getPathCollection().stream()
+ .filter(pathItemInfo -> !pathItemInfo.getID().equals("0"))
+ .map(BoxItem.Info::getName)
+ .collect(Collectors.joining("/"));
+ }
+
+ public static String getFolderPath(BoxFolder.Info folderInfo) {
+ final String parentFolderPath = getParentPath(folderInfo);
+ return "/".equals(parentFolderPath) ? parentFolderPath +
folderInfo.getName() : parentFolderPath + "/" + folderInfo.getName();
+ }
+
+ public static Map<String, String> createAttributeMap(BoxFile.Info
fileInfo) {
+ final Map<String, String> attributes = new LinkedHashMap<>();
+ attributes.put(BoxFileAttributes.ID, fileInfo.getID());
+ attributes.put(CoreAttributes.FILENAME.key(), fileInfo.getName());
+ attributes.put(CoreAttributes.PATH.key(), getParentPath(fileInfo));
+ attributes.put(BoxFileAttributes.TIMESTAMP,
valueOf(fileInfo.getModifiedAt()));
+ attributes.put(BoxFileAttributes.SIZE, valueOf(fileInfo.getSize()));
+ return attributes;
+ }
+
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFlowFileAttribute.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFlowFileAttribute.java
index e86ecc673a..be1fc46042 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFlowFileAttribute.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/BoxFlowFileAttribute.java
@@ -17,13 +17,14 @@
package org.apache.nifi.processors.box;
import java.util.function.Function;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
public enum BoxFlowFileAttribute {
- ID(BoxFileInfo.ID, BoxFileInfo::getId),
- FILENAME(BoxFileInfo.FILENAME, BoxFileInfo::getName),
- PATH(BoxFileInfo.PATH, BoxFileInfo::getPath),
- SIZE(BoxFileInfo.SIZE, fileInfo -> String.valueOf(fileInfo.getSize())),
- TIMESTAMP(BoxFileInfo.TIMESTAMP, fileInfo ->
String.valueOf(fileInfo.getTimestamp()));
+ ID(BoxFileAttributes.ID, BoxFileInfo::getId),
+ FILENAME(CoreAttributes.FILENAME.key(), BoxFileInfo::getName),
+ PATH(CoreAttributes.PATH.key(), BoxFileInfo::getPath),
+ SIZE(BoxFileAttributes.SIZE, fileInfo ->
String.valueOf(fileInfo.getSize())),
+ TIMESTAMP(BoxFileAttributes.TIMESTAMP, fileInfo ->
String.valueOf(fileInfo.getTimestamp()));
private final String name;
private final Function<BoxFileInfo, String> fromFileInfo;
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFile.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFile.java
index cad434b553..2ef2f567cf 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFile.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/FetchBoxFile.java
@@ -16,10 +16,26 @@
*/
package org.apache.nifi.processors.box;
+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;
+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;
+
import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxAPIResponseException;
import com.box.sdk.BoxFile;
+import java.util.concurrent.TimeUnit;
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;
@@ -37,7 +53,6 @@ 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.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -47,46 +62,51 @@ import java.util.Set;
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
@Tags({"box", "storage", "fetch"})
@CapabilityDescription("Fetches files from a Box Folder. Designed to be used
in tandem with ListBoxFile.")
-@SeeAlso({ListBoxFile.class})
+@SeeAlso({ListBoxFile.class, PutBoxFile.class})
+@ReadsAttribute(attribute = ID, description = ID_DESC)
@WritesAttributes({
- @WritesAttribute(attribute = FetchBoxFile.ERROR_CODE_ATTRIBUTE,
description = "The error code returned by Box when the fetch of a file fails"),
- @WritesAttribute(attribute = FetchBoxFile.ERROR_MESSAGE_ATTRIBUTE,
description = "The error message returned by Box when the fetch of a file
fails")
+ @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 = ERROR_CODE, description =
ERROR_CODE_DESC),
+ @WritesAttribute(attribute = ERROR_MESSAGE, description =
ERROR_MESSAGE_DESC)
})
public class FetchBoxFile extends AbstractProcessor {
- public static final String ERROR_CODE_ATTRIBUTE = "error.code";
- public static final String ERROR_MESSAGE_ATTRIBUTE = "error.message";
public static final PropertyDescriptor FILE_ID = new PropertyDescriptor
- .Builder().name("box-file-id")
- .displayName("File ID")
- .description("The ID of the File to fetch")
- .required(true)
- .defaultValue("${box.id}")
-
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
- .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
- .build();
+ .Builder().name("box-file-id")
+ .displayName("File ID")
+ .description("The ID of the File to fetch")
+ .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 for each successfully
fetched File.")
- .build();
+ new Relationship.Builder()
+ .name("success")
+ .description("A FlowFile will be routed here for each
successfully fetched File.")
+ .build();
public static final Relationship REL_FAILURE =
- new Relationship.Builder().name("failure")
- .description("A FlowFile will be routed here for each File for
which fetch was attempted but failed.")
- .build();
+ new Relationship.Builder()
+ .name("failure")
+ .description("A FlowFile will be routed here for each File
for which fetch was attempted but failed.")
+ .build();
+
+ public static final Set<Relationship> RELATIONSHIPS =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ REL_SUCCESS,
+ REL_FAILURE
+ )));
private static final List<PropertyDescriptor> PROPERTIES =
Collections.unmodifiableList(Arrays.asList(
- BoxClientService.BOX_CLIENT_SERVICE,
- FILE_ID
+ BoxClientService.BOX_CLIENT_SERVICE,
+ FILE_ID
));
- public static final Set<Relationship> relationships =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
- REL_SUCCESS,
- REL_FAILURE
- )));
-
private volatile BoxAPIConnection boxAPIConnection;
@Override
@@ -96,11 +116,11 @@ public class FetchBoxFile extends AbstractProcessor {
@Override
public Set<Relationship> getRelationships() {
- return relationships;
+ return RELATIONSHIPS;
}
@OnScheduled
- public void onScheduled(final ProcessContext context) throws IOException {
+ public void onScheduled(final ProcessContext context) {
BoxClientService boxClientService =
context.getProperty(BoxClientService.BOX_CLIENT_SERVICE).asControllerService(BoxClientService.class);
boxAPIConnection = boxClientService.getBoxApiConnection();
@@ -114,11 +134,13 @@ public class FetchBoxFile extends AbstractProcessor {
}
String fileId =
context.getProperty(FILE_ID).evaluateAttributeExpressions(flowFile).getValue();
- FlowFile outFlowFile = flowFile;
try {
- outFlowFile = fetchFile(fileId, session, outFlowFile);
-
- session.transfer(outFlowFile, REL_SUCCESS);
+ final long startNanos = System.nanoTime();
+ flowFile = fetchFile(fileId, session, flowFile);
+ final String boxUrlOfFile = BoxFileUtils.BOX_URL + fileId;
+ final long transferMillis =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+ session.getProvenanceReporter().fetch(flowFile, boxUrlOfFile,
transferMillis);
+ session.transfer(flowFile, REL_SUCCESS);
} catch (BoxAPIResponseException e) {
handleErrorResponse(session, fileId, flowFile, e);
} catch (Exception e) {
@@ -126,28 +148,31 @@ public class FetchBoxFile extends AbstractProcessor {
}
}
- FlowFile fetchFile(String fileId, ProcessSession session, FlowFile
outFlowFile) {
- BoxFile boxFile = new BoxFile(boxAPIConnection, fileId);
-
- outFlowFile = session.write(outFlowFile, outputStream ->
boxFile.download(outputStream));
-
- return outFlowFile;
+ BoxFile getBoxFile(String fileId) {
+ return new BoxFile(boxAPIConnection, fileId);
}
- private void handleErrorResponse(ProcessSession session, String fileId,
FlowFile outFlowFile, BoxAPIResponseException e) {
- getLogger().error("Couldn't fetch file with id '{}'", fileId, e);
+ private FlowFile fetchFile(String fileId, ProcessSession session, FlowFile
flowFile) {
+ final BoxFile boxFile = getBoxFile(fileId);
+ flowFile = session.write(flowFile, outputStream ->
boxFile.download(outputStream));
+ flowFile = session.putAllAttributes(flowFile,
BoxFileUtils.createAttributeMap(boxFile.getInfo()));
+ return flowFile;
+ }
- outFlowFile = session.putAttribute(outFlowFile, ERROR_CODE_ATTRIBUTE,
"" + e.getResponseCode());
- outFlowFile = session.putAttribute(outFlowFile,
ERROR_MESSAGE_ATTRIBUTE, e.getMessage());
+ private void handleErrorResponse(ProcessSession session, String fileId,
FlowFile flowFile, BoxAPIResponseException e) {
+ getLogger().error("Couldn't fetch file with id [{}]", fileId, e);
- session.transfer(outFlowFile, REL_FAILURE);
+ flowFile = session.putAttribute(flowFile, ERROR_CODE,
valueOf(e.getResponseCode()));
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
}
private void handleUnexpectedError(ProcessSession session, FlowFile
flowFile, String fileId, Exception e) {
- getLogger().error("Unexpected error while fetching and processing file
with id '{}'", fileId, e);
-
- flowFile = session.putAttribute(flowFile, ERROR_MESSAGE_ATTRIBUTE,
e.getMessage());
+ getLogger().error("Failed fetching and processing file with id [{}]",
fileId, e);
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.penalize(flowFile);
session.transfer(flowFile, REL_FAILURE);
}
}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFile.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFile.java
index d023af04a2..192c99ab55 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFile.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFile.java
@@ -16,10 +16,28 @@
*/
package org.apache.nifi.processors.box;
+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;
+
import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxFile;
import com.box.sdk.BoxFolder;
import com.box.sdk.BoxItem;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
import org.apache.nifi.annotation.behavior.PrimaryNodeOnly;
@@ -44,18 +62,6 @@ import
org.apache.nifi.processor.util.list.ListedEntityTracker;
import org.apache.nifi.scheduling.SchedulingStrategy;
import org.apache.nifi.serialization.record.RecordSchema;
-import java.io.IOException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
@PrimaryNodeOnly
@TriggerSerially
@Tags({"box", "storage"})
@@ -64,13 +70,14 @@ import java.util.stream.Collectors;
"Or - in case the 'Record Writer' property is set - the entire result is
written as records to a single FlowFile. " +
"This Processor is designed to run on Primary Node only in a cluster. If
the primary node changes, the new Primary Node will pick up where the " +
"previous node left off without duplicating all of the data.")
-@SeeAlso({FetchBoxFile.class})
+@SeeAlso({FetchBoxFile.class, PutBoxFile.class})
@InputRequirement(Requirement.INPUT_FORBIDDEN)
-@WritesAttributes({@WritesAttribute(attribute = BoxFileInfo.ID, description =
"The id of the file"),
- @WritesAttribute(attribute = BoxFileInfo.FILENAME, description = "The name
of the file"),
- @WritesAttribute(attribute = BoxFileInfo.PATH, description = "The path of
the file on Box"),
- @WritesAttribute(attribute = BoxFileInfo.SIZE, description = "The size of
the file (in bytes)"),
- @WritesAttribute(attribute = BoxFileInfo.TIMESTAMP, description = "The
last modified time of the file.")})
+@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)})
@Stateful(scopes = {Scope.CLUSTER}, description = "The processor stores
necessary data to be able to keep track what files have been listed already." +
" What exactly needs to be stored depends on the 'Listing Strategy'.")
@DefaultSchedule(strategy = SchedulingStrategy.TIMER_DRIVEN, period = "1 min")
@@ -158,7 +165,7 @@ public class ListBoxFile extends
AbstractListProcessor<BoxFileInfo> {
}
@OnScheduled
- public void onScheduled(final ProcessContext context) throws IOException {
+ public void onScheduled(final ProcessContext context) {
BoxClientService boxClientService =
context.getProperty(BoxClientService.BOX_CLIENT_SERVICE).asControllerService(BoxClientService.class);
boxAPIConnection = boxClientService.getBoxApiConnection();
@@ -200,8 +207,8 @@ public class ListBoxFile extends
AbstractListProcessor<BoxFileInfo> {
protected List<BoxFileInfo> performListing(
final ProcessContext context,
final Long minTimestamp,
- final ListingMode listingMode
- ) throws IOException {
+ final ListingMode listingMode) {
+
final List<BoxFileInfo> listing = new ArrayList<>();
final String folderId =
context.getProperty(FOLDER_ID).evaluateAttributeExpressions().getValue();
@@ -235,10 +242,7 @@ public class ListBoxFile extends
AbstractListProcessor<BoxFileInfo> {
BoxFileInfo boxFileInfo = new BoxFileInfo.Builder()
.id(info.getID())
.fileName(info.getName())
- .path("/" + info.getPathCollection().stream()
- .filter(pathItemInfo ->
!pathItemInfo.getID().equals("0"))
- .map(BoxItem.Info::getName)
- .collect(Collectors.joining("/")))
+ .path(BoxFileUtils.getParentPath(info))
.size(info.getSize())
.createdTime(info.getCreatedAt().getTime())
.modifiedTime(info.getModifiedAt().getTime())
@@ -257,7 +261,7 @@ public class ListBoxFile extends
AbstractListProcessor<BoxFileInfo> {
}
@Override
- protected Integer countUnfilteredListing(final ProcessContext context)
throws IOException {
+ protected Integer countUnfilteredListing(final ProcessContext context) {
return performListing(context, null,
ListingMode.CONFIGURATION_VERIFICATION).size();
}
}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/PutBoxFile.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/PutBoxFile.java
new file mode 100644
index 0000000000..13e7609120
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/PutBoxFile.java
@@ -0,0 +1,429 @@
+/*
+ * 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 static java.lang.String.format;
+import static java.lang.String.valueOf;
+import static java.util.Arrays.asList;
+import static
org.apache.nifi.processor.util.StandardValidators.createRegexMatchingValidator;
+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;
+import static org.apache.nifi.processors.box.BoxFileUtils.BOX_URL;
+import static
org.apache.nifi.processors.conflict.resolution.ConflictResolutionStrategy.IGNORE;
+import static
org.apache.nifi.processors.conflict.resolution.ConflictResolutionStrategy.REPLACE;
+
+import com.box.sdk.BoxAPIConnection;
+import com.box.sdk.BoxAPIException;
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFile;
+import com.box.sdk.BoxFolder;
+import com.box.sdk.BoxItem;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import java.util.stream.StreamSupport;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
+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.DataUnit;
+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.apache.nifi.processors.conflict.resolution.ConflictResolutionStrategy;
+
+
+@SeeAlso({ListBoxFile.class, FetchBoxFile.class})
+@InputRequirement(Requirement.INPUT_REQUIRED)
+@Tags({"box", "storage", "put"})
+@CapabilityDescription("Puts content to a Box folder.")
+@ReadsAttribute(attribute = "filename", description = "Uses the FlowFile's
filename as the filename for the Box object.")
+@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 = ERROR_CODE, description =
ERROR_CODE_DESC),
+ @WritesAttribute(attribute = ERROR_MESSAGE, description =
ERROR_MESSAGE_DESC)})
+public class PutBoxFile extends AbstractProcessor {
+ public static final int CHUNKED_UPLOAD_LOWER_LIMIT_IN_BYTES = 20 * 1024 *
1024;
+ public static final int CHUNKED_UPLOAD_UPPER_LIMIT_IN_BYTES = 50 * 1024 *
1024;
+
+ public static final int NUMBER_OF_RETRIES = 10;
+ public static final int WAIT_TIME_MS = 1000;
+
+ public static final PropertyDescriptor FOLDER_ID = new
PropertyDescriptor.Builder()
+ .name("box-folder-id")
+ .displayName("Folder ID")
+ .description("The ID of the folder where the file is uploaded." +
+ " Please see Additional Details to obtain Folder ID.")
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+ .required(true)
+ .build();
+
+ public static final PropertyDescriptor FILE_NAME = new
PropertyDescriptor.Builder()
+ .name("file-name")
+ .displayName("Filename")
+ .description("The name of the file to upload to the specified Box
folder.")
+ .required(true)
+ .defaultValue("${filename}")
+
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final PropertyDescriptor SUBFOLDER_NAME = new
PropertyDescriptor.Builder()
+ .name("subfolder-name")
+ .displayName("Subfolder Name")
+ .description("The name (path) of the subfolder where files are
uploaded. The subfolder name is relative to the folder specified by 'Folder
ID'."
+ + " Example: subFolder, subFolder1/subfolder2")
+
.addValidator(createRegexMatchingValidator(Pattern.compile("^(?!/).+(?<!/)$"),
false,
+ "Subfolder Name should not contain leading or trailing
slash ('/') character."))
+
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+ .required(false)
+ .build();
+
+ public static final PropertyDescriptor CREATE_SUBFOLDER = new
PropertyDescriptor.Builder()
+ .name("create-folder")
+ .displayName("Create Subfolder")
+ .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+ .required(true)
+ .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
+ .allowableValues("true", "false")
+ .defaultValue("false")
+ .dependsOn(SUBFOLDER_NAME)
+ .description("Specifies whether to check if the subfolder exists
and to automatically create it if it does not. " +
+ "Permission to list folders is required. ")
+ .build();
+
+ public static final PropertyDescriptor CONFLICT_RESOLUTION = new
PropertyDescriptor.Builder()
+ .name("conflict-resolution-strategy")
+ .displayName("Conflict Resolution Strategy")
+ .description("Indicates what should happen when a file with the
same name already exists in the specified Box folder.")
+ .required(true)
+ .defaultValue(ConflictResolutionStrategy.FAIL.getValue())
+ .allowableValues(ConflictResolutionStrategy.class)
+ .build();
+
+ public static final PropertyDescriptor CHUNKED_UPLOAD_THRESHOLD = new
PropertyDescriptor.Builder()
+ .name("chunked-upload-threshold")
+ .displayName("Chunked Upload Threshold")
+ .description("The maximum size of the content which is uploaded at
once. FlowFiles larger than this threshold are uploaded in chunks."
+ + " Chunked upload is allowed for files larger than 20 MB.
It is recommended to use chunked upload for files exceeding 50 MB.")
+ .defaultValue("20 MB")
+
.addValidator(StandardValidators.createDataSizeBoundsValidator(CHUNKED_UPLOAD_LOWER_LIMIT_IN_BYTES,
CHUNKED_UPLOAD_UPPER_LIMIT_IN_BYTES))
+ .required(false)
+ .build();
+
+ public static final List<PropertyDescriptor> PROPERTIES =
Collections.unmodifiableList(asList(
+ BoxClientService.BOX_CLIENT_SERVICE,
+ FOLDER_ID,
+ SUBFOLDER_NAME,
+ CREATE_SUBFOLDER,
+ FILE_NAME,
+ CONFLICT_RESOLUTION,
+ CHUNKED_UPLOAD_THRESHOLD
+ ));
+
+ public static final Relationship REL_SUCCESS =
+ new Relationship.Builder()
+ .name("success")
+ .description("Files that have been successfully written to
Box are transferred to this relationship.")
+ .build();
+
+ public static final Relationship REL_FAILURE =
+ new Relationship.Builder()
+ .name("failure")
+ .description("Files that could not be written to Box for
some reason are transferred to this relationship.")
+ .build();
+
+ public static final Set<Relationship> RELATIONSHIPS =
Collections.unmodifiableSet(new HashSet<>(asList(
+ REL_SUCCESS,
+ REL_FAILURE
+ )));
+
+ private static final int CONFLICT_RESPONSE_CODE = 409;
+ private static final int NOT_FOUND_RESPONSE_CODE = 404;
+
+ private volatile BoxAPIConnection boxAPIConnection;
+
+ @Override
+ public Set<Relationship> getRelationships() {
+ return RELATIONSHIPS;
+ }
+
+ @Override
+ public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTIES;
+ }
+
+ @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 filename =
context.getProperty(FILE_NAME).evaluateAttributeExpressions(flowFile).getValue();
+ final long chunkUploadThreshold =
context.getProperty(CHUNKED_UPLOAD_THRESHOLD)
+ .asDataSize(DataUnit.B)
+ .longValue();
+ final ConflictResolutionStrategy conflictResolution =
ConflictResolutionStrategy.forValue(context.getProperty(CONFLICT_RESOLUTION).getValue());
+
+ final long startNanos = System.nanoTime();
+ String fullPath = null;
+
+ try {
+ final long size = flowFile.getSize();
+ final BoxFolder parentFolder =
getOrCreateDirectParentFolder(context, flowFile);
+ fullPath = BoxFileUtils.getFolderPath(parentFolder.getInfo());
+ BoxFile.Info uploadedFileInfo = null;
+
+ try (InputStream rawIn = session.read(flowFile)){
+
+ if (REPLACE.equals(conflictResolution)) {
+ uploadedFileInfo = replaceBoxFileIfExists(parentFolder,
filename, rawIn, size, chunkUploadThreshold);
+ }
+
+ if (uploadedFileInfo == null) {
+ uploadedFileInfo = createBoxFile(parentFolder, filename,
rawIn, size, chunkUploadThreshold);
+ }
+ } catch (BoxAPIResponseException e) {
+ if (e.getResponseCode() == CONFLICT_RESPONSE_CODE) {
+ handleConflict(conflictResolution, filename, fullPath, e);
+ } else {
+ throw e;
+ }
+ }
+
+ if (uploadedFileInfo != null) {
+ final Map<String, String> attributes =
BoxFileUtils.createAttributeMap(uploadedFileInfo);
+ final String url = BOX_URL + uploadedFileInfo.getID();
+ flowFile = session.putAllAttributes(flowFile, attributes);
+ final long transferMillis =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+ session.getProvenanceReporter().send(flowFile, url,
transferMillis);
+ }
+
+ session.transfer(flowFile, REL_SUCCESS);
+ } catch (BoxAPIResponseException e) {
+ getLogger().error("Upload failed: File [{}] Folder [{}] Response
Code [{}]", filename, fullPath, e.getResponseCode(), e);
+ handleExpectedError(session, flowFile, e);
+ } catch (Exception e) {
+ getLogger().error("Upload failed: File [{}], Folder [{}]",
filename, fullPath, e);
+ handleUnexpectedError(session, flowFile, e);
+ }
+ }
+
+ BoxFolder getFolder(String folderId) {
+ return new BoxFolder(boxAPIConnection, folderId);
+ }
+
+ private BoxFolder getOrCreateDirectParentFolder(ProcessContext context,
FlowFile flowFile ) {
+ final String subfolderPath =
context.getProperty(SUBFOLDER_NAME).evaluateAttributeExpressions(flowFile).getValue();
+ final boolean createFolder =
context.getProperty(CREATE_SUBFOLDER).asBoolean();
+ final String folderId =
context.getProperty(FOLDER_ID).evaluateAttributeExpressions(flowFile).getValue();
+ BoxFolder parentFolder = getFolderById(folderId);
+
+ if (subfolderPath != null) {
+ final Queue<String> subFolderNames =
getSubFolderNames(subfolderPath);
+ parentFolder = getOrCreateSubfolders(subFolderNames, parentFolder,
createFolder);
+ }
+
+ return parentFolder;
+ }
+
+ private BoxFile.Info replaceBoxFileIfExists(BoxFolder parentFolder, String
filename, final InputStream inputStream, final long size, final long
chunkUploadThreshold)
+ throws IOException, InterruptedException {
+ final Optional<BoxFile> existingBoxFileInfo = getFileByName(filename,
parentFolder);
+ if (existingBoxFileInfo.isPresent()) {
+ final BoxFile existingBoxFile = existingBoxFileInfo.get();
+
+ if (size > chunkUploadThreshold) {
+ return existingBoxFile.uploadLargeFile(inputStream, size);
+ } else {
+ return existingBoxFile.uploadNewVersion(inputStream);
+ }
+ }
+ return null;
+ }
+
+ private BoxFile.Info createBoxFile(BoxFolder parentFolder, String
filename, InputStream inputStream, long size, final long chunkUploadThreshold)
+ throws IOException, InterruptedException {
+ if (size > chunkUploadThreshold) {
+ return parentFolder.uploadLargeFile(inputStream, filename, size);
+ } else {
+ return parentFolder.uploadFile(inputStream, filename);
+ }
+ }
+
+ private Queue<String> getSubFolderNames(String subfolderPath) {
+ final Queue<String> subfolderNames = new LinkedList<>();
+ Collections.addAll(subfolderNames, subfolderPath.split("/"));
+ return subfolderNames;
+ }
+
+ private BoxFolder getOrCreateSubfolders(Queue<String> subFolderNames,
BoxFolder parentFolder, boolean createFolder) {
+ final BoxFolder newParentFolder =
getOrCreateFolder(subFolderNames.poll(), parentFolder, createFolder);
+
+ if (!subFolderNames.isEmpty()) {
+ return getOrCreateSubfolders(subFolderNames, newParentFolder,
createFolder);
+ } else {
+ return newParentFolder;
+ }
+ }
+
+ private BoxFolder getOrCreateFolder(String folderName, BoxFolder
parentFolder, boolean createFolder) {
+ final Optional<BoxFolder> existingFolder = getFolderByName(folderName,
parentFolder);
+
+ if (existingFolder.isPresent()) {
+ return existingFolder.get();
+ }
+
+ if (!createFolder) {
+ throw new ProcessException(format("The specified subfolder [%s]
does not exist and [%s] is false.",
+ folderName, CREATE_SUBFOLDER.getDisplayName()));
+ }
+
+ return createFolder(folderName, parentFolder);
+ }
+
+ private BoxFolder createFolder(final String folderName, final BoxFolder
parentFolder) {
+ getLogger().info("Creating Folder [{}], Parent [{}]", folderName,
parentFolder.getID());
+
+ try {
+ return parentFolder.createFolder(folderName).getResource();
+ } catch (BoxAPIResponseException e) {
+ if (e.getResponseCode() != CONFLICT_RESPONSE_CODE) {
+ throw e;
+ } else {
+ Optional<BoxFolder> createdFolder =
waitForOngoingFolderCreationToFinish(folderName, parentFolder);
+ return createdFolder.orElseThrow(() -> new
ProcessException(format("Created subfolder [%s] can not be found under [%s]",
+ folderName, parentFolder.getID())));
+ }
+ }
+ }
+
+ private Optional<BoxFolder> waitForOngoingFolderCreationToFinish(final
String folderName, final BoxFolder parentFolder) {
+ try {
+ Optional<BoxFolder> createdFolder = getFolderByName(folderName,
parentFolder);
+
+ for (int i = 0; i < NUMBER_OF_RETRIES &&
!createdFolder.isPresent(); i++) {
+ getLogger().debug("Subfolder [{}] under [{}] has not been
created yet, waiting {} ms",
+ folderName, parentFolder.getID(), WAIT_TIME_MS);
+ Thread.sleep(WAIT_TIME_MS);
+ createdFolder = getFolderByName(folderName, parentFolder);
+ }
+ return createdFolder;
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(format("Waiting for creation of
subfolder [%s] under [%s] was interrupted",
+ folderName, parentFolder.getID()), ie);
+ }
+ }
+
+ private BoxFolder getFolderById(final String folderId) {
+ final BoxFolder folder = getFolder(folderId);
+ try {
+ //Error is returned for nonexistent folder only when a method is
called on BoxFolder.
+ folder.getInfo();
+ } catch (BoxAPIResponseException e) {
+ if (e.getResponseCode() == NOT_FOUND_RESPONSE_CODE) {
+ throw new ProcessException(format("The Folder [%s] specified
by [%s] does not exist", folderId, FOLDER_ID.getDisplayName()));
+ }
+ }
+ return folder;
+ }
+
+ private Optional<BoxFolder> getFolderByName(final String folderName, final
BoxFolder parentFolder) {
+ return getItemByName(folderName, parentFolder, BoxFolder.Info.class)
+ .map(BoxFolder.Info::getResource);
+ }
+
+ private Optional<BoxFile> getFileByName(final String filename, final
BoxFolder parentFolder) {
+ return getItemByName(filename, parentFolder, BoxFile.Info.class)
+ .map(BoxFile.Info::getResource);
+ }
+
+ private <T extends BoxItem.Info> Optional<T> getItemByName(final String
itemName, final BoxFolder parentFolder, Class<T> type) {
+ return
StreamSupport.stream(parentFolder.getChildren("name").spliterator(), false)
+ .filter(type::isInstance)
+ .map(type::cast)
+ .filter(info -> info.getName().equals(itemName))
+ .findAny();
+ }
+
+ private void handleConflict(final ConflictResolutionStrategy
conflictResolution, final String filename, String path, final BoxAPIException
e) {
+ if (conflictResolution == IGNORE) {
+ getLogger().info("File with the same name [{}] already exists in
[{}]. Remote file is not modified due to [{}] being set to [{}]",
+ filename, path, CONFLICT_RESOLUTION.getDisplayName(),
conflictResolution.getDisplayName());
+ } else {
+ throw new ProcessException(format("File with the same name [%s]
already exists in [%s]", filename, path), e);
+ }
+ }
+
+ private void handleUnexpectedError(final ProcessSession session, FlowFile
flowFile, final Exception e) {
+ flowFile = session.putAttribute(flowFile,
BoxFileAttributes.ERROR_MESSAGE, e.getMessage());
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+
+ private void handleExpectedError(final ProcessSession session, FlowFile
flowFile, final BoxAPIResponseException e) {
+ flowFile = session.putAttribute(flowFile,
BoxFileAttributes.ERROR_MESSAGE, e.getMessage());
+ flowFile = session.putAttribute(flowFile,
BoxFileAttributes.ERROR_CODE, valueOf(e.getResponseCode()));
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+}
\ No newline at end of file
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index 09755f9be5..4bde2b7133 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -14,3 +14,4 @@
# limitations under the License.
org.apache.nifi.processors.box.ListBoxFile
org.apache.nifi.processors.box.FetchBoxFile
+org.apache.nifi.processors.box.PutBoxFile
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.FetchBoxFile/additionalDetails.html
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.FetchBoxFile/additionalDetails.html
new file mode 100644
index 0000000000..1b6c6916c2
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.FetchBoxFile/additionalDetails.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/html">
+<!--
+ 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.
+ -->
+
+<head>
+ <meta charset="utf-8"/>
+ <title>FetchBoxFile</title>
+ <link rel="stylesheet" href="../../../../../css/component-usage.css"
type="text/css"/>
+</head>
+<body>
+
+<h1>Fetch Box files in NiFi</h1>
+
+<ol>
+ <li><b>Find File ID</b>
+ </br>
+ Usually FetchBoxFile is used with ListBoxFile and 'box.id' is
set.</br></br>
+ In case 'box.id' is not available, you can find the ID of the file in
the following way:
+ </br>
+ <ul>
+ <li>Click on the file.</li>
+ <li>The URL in the browser will include the File ID.
+ </br>For example, if the URL were
<code>https://app.box.com/file/1012106094023?s=ldiqjwuor2vwdxeeap2rtcz66dql89h3</code>,</br>
+ the File ID would be <code>1012106094023</code>
+ </li>
+ </ul>
+ </li>
+ <li><b>Set File ID in 'File ID' property</b>
+ </li>
+</ol>
+
+</body>
+</html>
\ No newline at end of file
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.ListBoxFile/additionalDetails.html
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.ListBoxFile/additionalDetails.html
new file mode 100644
index 0000000000..ae29849671
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.ListBoxFile/additionalDetails.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/html">
+<!--
+ 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.
+ -->
+
+<head>
+ <meta charset="utf-8"/>
+ <title>ListBoxFile</title>
+ <link rel="stylesheet" href="../../../../../css/component-usage.css"
type="text/css"/>
+</head>
+<body>
+
+<h1>List Box folders in NiFi</h1>
+
+
+<ol>
+ <li><b>Find Folder ID</b>
+ <ul>
+ <li>Navigate to the folder to be listed in Box and enter it. The
URL in your browser will include the ID at the end of
+ the URL.
+ For example, if the URL were
<code>https://app.box.com/folder/191632099757</code>, the
+ Folder ID would be <code>191632099757</code>
+ </li>
+ </ul>
+ </li>
+ <li><b>Set Folder ID in 'Folder ID' property</b>
+ </li>
+</ol>
+
+</body>
+</html>
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.PutBoxFile/additionalDetails.html
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.PutBoxFile/additionalDetails.html
new file mode 100644
index 0000000000..32035e3950
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/docs/org.apache.nifi.processors.box.PutBoxFile/additionalDetails.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/html">
+<!--
+ 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.
+ -->
+
+<head>
+ <meta charset="utf-8"/>
+ <title>PutBoxFile</title>
+ <link rel="stylesheet" href="../../../../../css/component-usage.css"
type="text/css"/>
+</head>
+<body>
+
+<h1>Upload files to Box from NiFi</h1>
+
+<ol>
+ <li><b>Find Folder ID</b>
+ <ul>
+ <li>Navigate to the folder in Box and enter it. The URL in your
browser will include the ID at the end of
+ the URL.
+ For example, if the URL were
<code>https://app.box.com/folder/191632099757</code>, the
+ Folder ID would be <code>191632099757</code>
+ </li>
+ </ul>
+ </li>
+ <li><b>Set Folder ID in 'Folder ID' property</b>
+ </li>
+</ol>
+
+</body>
+</html>
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileIT.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileIT.java
index 8c0c94f567..053083f5c8 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileIT.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileIT.java
@@ -16,28 +16,27 @@
*/
package org.apache.nifi.processors.box;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxConfig;
import com.box.sdk.BoxDeveloperEditionAPIConnection;
import com.box.sdk.BoxFile;
import com.box.sdk.BoxFolder;
-import org.apache.nifi.box.controllerservices.BoxClientService;
-import org.apache.nifi.processor.Processor;
-import org.apache.nifi.util.TestRunner;
-import org.apache.nifi.util.TestRunners;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-
import java.io.ByteArrayInputStream;
import java.io.FileReader;
import java.io.Reader;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import org.apache.nifi.box.controllerservices.BoxClientService;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
/**
* Set the following constants before running:<br />
@@ -54,7 +53,9 @@ public abstract class AbstractBoxFileIT<T extends Processor> {
static final String ACCOUNT_ID = "";
static final String APP_CONFIG_FILE = "";
- protected static final String DEFAULT_FILE_CONTENT = "test_content";
+ public static final String DEFAULT_FILE_CONTENT = "test_content";
+ public static final String CHANGED_FILE_CONTENT = "changed_test_content";
+ public static final String TEST_FILENAME = "test_filename.txt";
public static final String MAIN_FOLDER_NAME = "main";
protected T testSubject;
@@ -111,18 +112,17 @@ public abstract class AbstractBoxFileIT<T extends
Processor> {
protected BoxFolder.Info createFolder(String folderName, String
parentFolderId) {
BoxFolder parentFolder = new BoxFolder(boxAPIConnection,
parentFolderId);
BoxFolder.Info folderInfo = parentFolder.createFolder(folderName);
-
return folderInfo;
}
protected BoxFile.Info createFileWithDefaultContent(String name, String
folderId) {
- return createFile(name, DEFAULT_FILE_CONTENT, folderId);
+ return createFile(name, folderId);
}
- protected BoxFile.Info createFile(String name, String fileContent, String
folderId) {
+ protected BoxFile.Info createFile(String name, String folderId) {
BoxFolder folder = new BoxFolder(boxAPIConnection, folderId);
- BoxFile.Info fileInfo = folder.uploadFile(new
ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)), name);
+ BoxFile.Info fileInfo = folder.uploadFile(new
ByteArrayInputStream(DEFAULT_FILE_CONTENT.getBytes(UTF_8)), name);
return fileInfo;
}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileTest.java
new file mode 100644
index 0000000000..199ac02cf0
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/AbstractBoxFileTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 static java.lang.String.valueOf;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toSet;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import com.box.sdk.BoxAPIConnection;
+import com.box.sdk.BoxFile;
+import com.box.sdk.BoxFolder;
+import com.box.sdk.BoxFolder.Info;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.apache.nifi.box.controllerservices.BoxClientService;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.provenance.ProvenanceEventRecord;
+import org.apache.nifi.provenance.ProvenanceEventType;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class AbstractBoxFileTest {
+ public final String TEST_FILE_ID = "fileId";
+ public final String TEST_FOLDER_ID = "folderId";
+ public final String TEST_FILENAME = "filename";
+ public final String TEST_FOLDER_NAME = "folderName";
+ public final long TEST_SIZE = 12L;
+ public final long CREATED_TIME = 123456L;
+ public final long MODIFIED_TIME = 7891011L;
+ public final String CONTENT = "content";
+
+ protected TestRunner testRunner;
+
+ @Mock
+ protected BoxFolder mockBoxFolder;
+
+ @Mock
+ protected BoxClientService mockBoxClientService;
+
+ @Mock
+ protected BoxAPIConnection mockBoxAPIConnection;
+
+ @Mock
+ protected BoxFile.Info mockFileInfo;
+
+ @Mock
+ protected BoxFolder.Info mockBoxFolderInfo;
+
+
+ @BeforeEach
+ void setUp() throws Exception {
+
doReturn(mockBoxClientService.toString()).when(mockBoxClientService).getIdentifier();
+
doReturn(mockBoxAPIConnection).when(mockBoxClientService).getBoxApiConnection();
+
+ testRunner.addControllerService(mockBoxClientService.getIdentifier(),
mockBoxClientService);
+ testRunner.enableControllerService(mockBoxClientService);
+ testRunner.setProperty(BoxClientService.BOX_CLIENT_SERVICE,
mockBoxClientService.getIdentifier());
+ }
+
+ protected BoxFile.Info createFileInfo(String path, Long createdTime) {
+ return createFileInfo(path, createdTime,
singletonList(mockBoxFolderInfo));
+ }
+
+ protected BoxFile.Info createFileInfo(String path, Long createdTime,
List<Info> pathCollection) {
+ when(mockBoxFolderInfo.getName()).thenReturn(path);
+ when(mockBoxFolderInfo.getID()).thenReturn("not0");
+
+ when(mockFileInfo.getID()).thenReturn(TEST_FILE_ID);
+ when(mockFileInfo.getName()).thenReturn(TEST_FILENAME);
+ when(mockFileInfo.getPathCollection()).thenReturn(pathCollection);
+ when(mockFileInfo.getSize()).thenReturn(TEST_SIZE);
+ when(mockFileInfo.getModifiedAt()).thenReturn(new Date(createdTime));
+
+ return mockFileInfo;
+ }
+
+ protected void assertProvenanceEvent(ProvenanceEventType eventType) {
+ Set<ProvenanceEventType> expectedEventTypes =
Collections.singleton(eventType);
+ Set<ProvenanceEventType> actualEventTypes =
testRunner.getProvenanceEvents().stream()
+ .map(ProvenanceEventRecord::getEventType)
+ .collect(toSet());
+ assertEquals(expectedEventTypes, actualEventTypes);
+ }
+
+ protected void assertNoProvenanceEvent() {
+ assertTrue(testRunner.getProvenanceEvents().isEmpty());
+ }
+
+ protected void assertOutFlowFileAttributes(MockFlowFile flowFile) {
+ assertOutFlowFileAttributes(flowFile, "/" + TEST_FOLDER_NAME);
+ }
+
+ protected void assertOutFlowFileAttributes(MockFlowFile flowFile, String
path) {
+ flowFile.assertAttributeEquals(BoxFileAttributes.ID, TEST_FILE_ID);
+ flowFile.assertAttributeEquals(CoreAttributes.FILENAME.key(),
TEST_FILENAME);
+ flowFile.assertAttributeEquals(CoreAttributes.PATH.key(), path);
+ flowFile.assertAttributeEquals(BoxFileAttributes.TIMESTAMP,
valueOf(new Date(MODIFIED_TIME)));
+ flowFile.assertAttributeEquals(BoxFileAttributes.SIZE,
valueOf(TEST_SIZE));
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileIT.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileIT.java
index fb34d46344..a1773f5fbb 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileIT.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileIT.java
@@ -16,17 +16,18 @@
*/
package org.apache.nifi.processors.box;
-import com.box.sdk.BoxFile;
-import org.apache.nifi.util.MockFlowFile;
-import org.junit.jupiter.api.Test;
+import static java.util.Collections.singletonList;
-import java.util.Arrays;
+import com.box.sdk.BoxFile;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.util.MockFlowFile;
+import org.junit.jupiter.api.Test;
/**
* See Javadoc {@link AbstractBoxFileIT} for instructions how to run this test.
@@ -40,22 +41,19 @@ public class FetchBoxFileIT extends
AbstractBoxFileIT<FetchBoxFile> {
}
@Test
- void testFetchSingleFile() throws Exception {
- // GIVEN
+ void testFetchSingleFile() {
BoxFile.Info file = createFileWithDefaultContent("test_file.txt",
mainFolderId);
Map<String, String> inputFlowFileAttributes = new HashMap<>();
- inputFlowFileAttributes.put("box.id", file.getID());
- inputFlowFileAttributes.put("filename", file.getName());
+ inputFlowFileAttributes.put(BoxFileAttributes.ID, file.getID());
+ inputFlowFileAttributes.put(CoreAttributes.FILENAME.key(),
file.getName());
- HashSet<Map<String, String>> expectedAttributes = new
HashSet<>(Arrays.asList(inputFlowFileAttributes));
- List<String> expectedContent = Arrays.asList(DEFAULT_FILE_CONTENT);
+ HashSet<Map<String, String>> expectedAttributes = new
HashSet<>(singletonList(inputFlowFileAttributes));
+ List<String> expectedContent = singletonList(DEFAULT_FILE_CONTENT);
- // WHEN
testRunner.enqueue("unimportant_data", inputFlowFileAttributes);
testRunner.run();
- // THEN
testRunner.assertTransferCount(FetchBoxFile.REL_FAILURE, 0);
testRunner.assertAttributes(FetchBoxFile.REL_SUCCESS,
getCheckedAttributeNames(), expectedAttributes);
@@ -63,41 +61,38 @@ public class FetchBoxFileIT extends
AbstractBoxFileIT<FetchBoxFile> {
}
@Test
- void testInputFlowFileReferencesMissingFile() throws Exception {
- // GIVEN
+ void testInputFlowFileReferencesMissingFile() {
Map<String, String> inputFlowFileAttributes = new HashMap<>();
- inputFlowFileAttributes.put("box.id", "111");
- inputFlowFileAttributes.put("filename", "missing_filename");
+ inputFlowFileAttributes.put(BoxFileAttributes.ID, "111");
+ inputFlowFileAttributes.put(CoreAttributes.FILENAME.key(),
"missing_filename");
- Set<Map<String, String>> expectedFailureAttributes = new
HashSet<>(Arrays.asList(
+ Set<Map<String, String>> expectedFailureAttributes = new
HashSet<>(singletonList(
new HashMap<String, String>() {{
- put("box.id", "111");
- put("filename", "missing_filename");
- put("error.code", "404");
+ put(BoxFileAttributes.ID, "111");
+ put(CoreAttributes.FILENAME.key(), "missing_filename");
+ put(BoxFileAttributes.ERROR_CODE, "404");
}}
));
- // WHEN
+
testRunner.enqueue("unimportant_data", inputFlowFileAttributes);
testRunner.run();
- // THEN
testRunner.assertTransferCount(FetchBoxFile.REL_SUCCESS, 0);
testRunner.assertAttributes(FetchBoxFile.REL_FAILURE,
getCheckedAttributeNames(), expectedFailureAttributes);
}
@Test
- void testInputFlowFileThrowsExceptionBeforeFetching() throws Exception {
- // GIVEN
+ void testInputFlowFileThrowsExceptionBeforeFetching() {
BoxFile.Info file = createFileWithDefaultContent("test_file.txt",
mainFolderId);
Map<String, String> inputFlowFileAttributes = new HashMap<>();
- inputFlowFileAttributes.put("box.id", file.getID());
- inputFlowFileAttributes.put("filename", file.getName());
+ inputFlowFileAttributes.put(BoxFileAttributes.ID, file.getID());
+ inputFlowFileAttributes.put(CoreAttributes.FILENAME.key(),
file.getName());
MockFlowFile input = new MockFlowFile(1) {
- AtomicBoolean throwException = new AtomicBoolean(true);
+ final AtomicBoolean throwException = new AtomicBoolean(true);
@Override
public boolean isPenalized() {
@@ -116,17 +111,15 @@ public class FetchBoxFileIT extends
AbstractBoxFileIT<FetchBoxFile> {
}
};
- Set<Map<String, String>> expectedFailureAttributes = new
HashSet<>(Arrays.asList(
+ Set<Map<String, String>> expectedFailureAttributes = new
HashSet<>(singletonList(
new HashMap<String, String>() {{
putAll(inputFlowFileAttributes);
}}
));
- // WHEN
testRunner.enqueue(input);
testRunner.run();
- // THEN
testRunner.assertTransferCount(FetchBoxFile.REL_SUCCESS, 0);
testRunner.assertAttributes(FetchBoxFile.REL_FAILURE,
getCheckedAttributeNames(), expectedFailureAttributes);
@@ -137,7 +130,7 @@ public class FetchBoxFileIT extends
AbstractBoxFileIT<FetchBoxFile> {
checkedAttributeNames.add(BoxFlowFileAttribute.ID.getName());
checkedAttributeNames.add(BoxFlowFileAttribute.FILENAME.getName());
- checkedAttributeNames.add(FetchBoxFile.ERROR_CODE_ATTRIBUTE);
+ checkedAttributeNames.add(BoxFileAttributes.ERROR_CODE);
return checkedAttributeNames;
}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileTest.java
new file mode 100644
index 0000000000..7b45eb85c8
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FetchBoxFileTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+import com.box.sdk.BoxFile;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.nifi.provenance.ProvenanceEventType;
+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;
+
+@ExtendWith(MockitoExtension.class)
+public class FetchBoxFileTest extends AbstractBoxFileTest{
+ @Mock
+ BoxFile mockBoxFile;
+
+ @BeforeEach
+ void setUp() throws Exception {
+
+ final FetchBoxFile testSubject = new FetchBoxFile() {
+ @Override
+ protected BoxFile getBoxFile(String fileId) {
+ return mockBoxFile;
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+ super.setUp();
+ }
+
+ @Test
+ void testBoxIdFromFlowFileAttribute() {
+ testRunner.setProperty(FetchBoxFile.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);
+
+ final BoxFile.Info fetchedFileInfo = createFileInfo(TEST_FOLDER_NAME,
MODIFIED_TIME);
+ doReturn(fetchedFileInfo).when(mockBoxFile).getInfo();
+
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFile.REL_SUCCESS, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ assertOutFlowFileAttributes(ff0);
+ verify(mockBoxFile).download(any(OutputStream.class));
+ assertProvenanceEvent(ProvenanceEventType.FETCH);
+ }
+
+ @Test
+ void testBoxIdFromProperty() {
+ testRunner.setProperty(FetchBoxFile.FILE_ID, TEST_FILE_ID);
+
+ final BoxFile.Info fetchedFileInfo = createFileInfo(TEST_FOLDER_NAME,
MODIFIED_TIME);
+ doReturn(fetchedFileInfo).when(mockBoxFile).getInfo();
+
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFile.REL_SUCCESS, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ assertOutFlowFileAttributes(ff0);
+ verify(mockBoxFile).download(any(OutputStream.class));
+ assertProvenanceEvent(ProvenanceEventType.FETCH);
+ }
+
+ @Test
+ void testFileDownloadFailure() {
+ testRunner.setProperty(FetchBoxFile.FILE_ID, TEST_FILE_ID);
+
+ doThrow(new RuntimeException("Download
failed")).when(mockBoxFile).download(any(OutputStream.class));
+
+
+ MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+
+ testRunner.assertAllFlowFilesTransferred(FetchBoxFile.REL_FAILURE, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(FetchBoxFile.REL_FAILURE);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ ff0.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE, "Download
failed");
+ assertNoProvenanceEvent();
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/SimpleListBoxFileTestTrait.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FileListingTestTrait.java
similarity index 86%
rename from
nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/SimpleListBoxFileTestTrait.java
rename to
nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FileListingTestTrait.java
index 64a2474927..003d337b18 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/SimpleListBoxFileTestTrait.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/FileListingTestTrait.java
@@ -16,20 +16,19 @@
*/
package org.apache.nifi.processors.box;
+import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import com.box.sdk.BoxFile;
import com.box.sdk.BoxFolder;
-
-import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public interface SimpleListBoxFileTestTrait {
+public interface FileListingTestTrait {
BoxFolder getMockBoxFolder();
default void mockFetchedFileList(
@@ -40,17 +39,15 @@ public interface SimpleListBoxFileTestTrait {
Long createdTime,
Long modifiedTime
) {
- doReturn(
- Arrays.asList(
- createFileInfo(
- id,
- filename,
- pathParts,
- size,
- createdTime,
- modifiedTime
+ doReturn(singletonList(createFileInfo(
+ id,
+ filename,
+ pathParts,
+ size,
+ createdTime,
+ modifiedTime
+ )
)
- )
).when(getMockBoxFolder()).getChildren("id",
"name",
"item_status",
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileIT.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileIT.java
index c0337524d6..0692c731e3 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileIT.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileIT.java
@@ -49,7 +49,6 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
@Test
void listFilesFrom3LayerDeepDirectoryTree() throws Exception {
- // GIVEN
BoxFolder.Info main_sub1 = createFolder("main_sub1", mainFolderId);
BoxFolder.Info main_sub2 = createFolder("main_sub2", mainFolderId);
@@ -78,10 +77,8 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
// The creation of the files are not (completely) synchronized.
Thread.sleep(2000);
- // WHEN
testRunner.run();
- // THEN
Set<String> actualFileNames = getActualFileNames();
assertEquals(expectedFileNames, actualFileNames);
@@ -89,19 +86,16 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
// Next, list a sub folder, non-recursively this time. (Changing these
properties will clear the Processor state as well
// so all files are eligible for listing again.)
- // GIVEN
testRunner.clearTransferState();
expectedFileNames = new HashSet<>(Arrays.asList(
"main_sub1_file1"
));
- // WHEN
testRunner.setProperty(ListBoxFile.FOLDER_ID, main_sub1.getID());
testRunner.setProperty(ListBoxFile.RECURSIVE_SEARCH, "false");
testRunner.run();
- // THEN
actualFileNames = getActualFileNames();
assertEquals(expectedFileNames, actualFileNames);
@@ -109,7 +103,6 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
@Test
void doNotListTooYoungFilesWhenMinAgeIsSet() throws Exception {
- // GIVEN
testRunner.setProperty(ListBoxFile.MIN_AGE, "15 s");
createFileWithDefaultContent("main_file", mainFolderId);
@@ -117,27 +110,22 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
// Make sure the file 'arrives' and could be listed
Thread.sleep(5000);
- // WHEN
testRunner.run();
- // THEN
Set<String> actualFileNames = getActualFileNames();
assertEquals(Collections.emptySet(), actualFileNames);
// Next, wait for another 10+ seconds for MIN_AGE to expire then list
again
- // GIVEN
Thread.sleep(10000);
Set<String> expectedFileNames = new HashSet<>(Arrays.asList(
"main_file"
));
- // WHEN
testRunner.run();
- // THEN
actualFileNames = getActualFileNames();
assertEquals(expectedFileNames, actualFileNames);
@@ -152,5 +140,4 @@ public class ListBoxFileIT extends
AbstractBoxFileIT<ListBoxFile> {
return actualFileNames;
}
-
}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileSimpleTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileListingTest.java
similarity index 84%
rename from
nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileSimpleTest.java
rename to
nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileListingTest.java
index 0fb2f7daa1..e58dedc71d 100644
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileSimpleTest.java
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileListingTest.java
@@ -16,44 +16,44 @@
*/
package org.apache.nifi.processors.box;
+import static java.util.Collections.singletonList;
+import static org.apache.nifi.util.EqualsWrapper.wrapList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxFolder;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
import org.apache.nifi.box.controllerservices.BoxClientService;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.util.EqualsWrapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
-import static org.apache.nifi.util.EqualsWrapper.wrapList;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-public class ListBoxFileSimpleTest implements SimpleListBoxFileTestTrait {
+@ExtendWith(MockitoExtension.class)
+public class ListBoxFileListingTest implements FileListingTestTrait {
private ListBoxFile testSubject;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ProcessContext mockProcessContext;
+ @Mock
private BoxClientService mockBoxClientService;
+ @Mock
private PropertyValue mockBoxClientServicePropertyValue;
+ @Mock
private BoxAPIConnection mockBoxAPIConnection;
-
+ @Mock
private BoxFolder mockBoxFolder;
@BeforeEach
- void setUp() throws Exception {
- mockProcessContext = mock(ProcessContext.class,
Answers.RETURNS_DEEP_STUBS);
- mockBoxClientService = mock(BoxClientService.class);
- mockBoxClientServicePropertyValue = mock(PropertyValue.class);
- mockBoxAPIConnection = mock(BoxAPIConnection.class);
-
- mockBoxFolder = mock(BoxFolder.class);
-
+ void setUp() {
testSubject = new ListBoxFile() {
@Override
BoxFolder getFolder(String folderId) {
@@ -69,20 +69,20 @@ public class ListBoxFileSimpleTest implements
SimpleListBoxFileTestTrait {
}
@Test
- void testCreatedListableEntityContainsCorrectData() throws Exception {
- // GIVEN
+ void testCreatedListableEntityContainsCorrectData() {
+
Long minTimestamp = 0L;
String id = "id_1";
String filename = "file_name_1";
List<String> pathParts = Arrays.asList("path", "to", "file");
- Long size = 125L;
+ long size = 125L;
long createdTime = 123456L;
long modifiedTime = 234567L;
mockFetchedFileList(id, filename, pathParts, size, createdTime,
modifiedTime);
- List<BoxFileInfo> expected = Arrays.asList(
+ List<BoxFileInfo> expected = singletonList(
new BoxFileInfo.Builder()
.id(id)
.fileName(filename)
@@ -93,10 +93,8 @@ public class ListBoxFileSimpleTest implements
SimpleListBoxFileTestTrait {
.build()
);
- // WHEN
List<BoxFileInfo> actual =
testSubject.performListing(mockProcessContext, minTimestamp, null);
- // THEN
List<Function<BoxFileInfo, Object>> propertyProviders = Arrays.asList(
BoxFileInfo::getId,
BoxFileInfo::getIdentifier,
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTest.java
new file mode 100644
index 0000000000..fed2265d6f
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 static java.lang.String.valueOf;
+import static java.util.Collections.singletonList;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ID;
+import static org.apache.nifi.processors.box.BoxFileAttributes.SIZE;
+import static org.apache.nifi.processors.box.BoxFileAttributes.TIMESTAMP;
+
+import com.box.sdk.BoxFolder;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.json.JsonRecordSetWriter;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.serialization.RecordSetWriterFactory;
+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.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class ListBoxFileTest extends AbstractBoxFileTest implements
FileListingTestTrait {
+
+ @BeforeEach
+ void setUp() throws Exception {
+
+ final ListBoxFile testSubject = new ListBoxFile() {
+ @Override
+ BoxFolder getFolder(String folderId) {
+ return mockBoxFolder;
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+ super.setUp();
+ testRunner.setProperty(ListBoxFile.FOLDER_ID, TEST_FOLDER_ID);
+ }
+
+ @Test
+ void testOutputAsAttributesWhereTimestampIsModifiedTime() {
+ final List<String> pathParts = Arrays.asList("path", "to", "file");
+ mockFetchedFileList(TEST_FILE_ID, TEST_FILENAME, pathParts, TEST_SIZE,
CREATED_TIME, MODIFIED_TIME);
+
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(ListBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 =
testRunner.getFlowFilesForRelationship(ListBoxFile.REL_SUCCESS).get(0);
+
+ ff0.assertAttributeEquals(ID, TEST_FILE_ID);
+ ff0.assertAttributeEquals(CoreAttributes.FILENAME.key(),
TEST_FILENAME);
+ ff0.assertAttributeEquals(CoreAttributes.PATH.key(), "/path/to/file");
+ ff0.assertAttributeEquals(SIZE, valueOf(TEST_SIZE));
+ ff0.assertAttributeEquals(TIMESTAMP, valueOf(MODIFIED_TIME));
+ }
+
+ @Test
+ void testOutputAsContent() throws Exception {
+ final List<String> pathParts = Arrays.asList("path", "to", "file");
+
+ addJsonRecordWriterFactory();
+
+ mockFetchedFileList(TEST_FILE_ID, TEST_FILENAME, pathParts, TEST_SIZE,
CREATED_TIME, MODIFIED_TIME);
+
+ final List<String> expectedContents = singletonList(
+ "[" +
+ "{" +
+ "\"box.id\":\"" + TEST_FILE_ID + "\"," +
+ "\"filename\":\"" + TEST_FILENAME + "\"," +
+ "\"path\":\"/path/to/file\"," +
+ "\"box.size\":" + TEST_SIZE + "," +
+ "\"box.timestamp\":" + MODIFIED_TIME +
+ "}" +
+ "]");
+
+
+ testRunner.run();
+
+ testRunner.assertContents(ListBoxFile.REL_SUCCESS, expectedContents);
+ }
+
+ private void addJsonRecordWriterFactory() throws InitializationException {
+ final RecordSetWriterFactory recordSetWriter = new
JsonRecordSetWriter();
+ testRunner.addControllerService("record_writer", recordSetWriter);
+ testRunner.enableControllerService(recordSetWriter);
+ testRunner.setProperty(ListBoxFile.RECORD_WRITER, "record_writer");
+ }
+
+ @Override
+ public BoxFolder getMockBoxFolder() {
+ return mockBoxFolder;
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTestRunnerTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTestRunnerTest.java
deleted file mode 100644
index bfab87c957..0000000000
---
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileTestRunnerTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.BoxFolder;
-import org.apache.nifi.box.controllerservices.BoxClientService;
-import org.apache.nifi.json.JsonRecordSetWriter;
-import org.apache.nifi.reporting.InitializationException;
-import org.apache.nifi.serialization.RecordSetWriterFactory;
-import org.apache.nifi.util.TestRunner;
-import org.apache.nifi.util.TestRunners;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-public class ListBoxFileTestRunnerTest implements SimpleListBoxFileTestTrait {
- private ListBoxFile testSubject;
- private TestRunner testRunner;
-
- private BoxAPIConnection mockBoxAPIConnection;
-
- private BoxFolder mockBoxFolder;
-
- private String folderId = "folderId";
-
- @BeforeEach
- void setUp() throws Exception {
- mockBoxAPIConnection = mock(BoxAPIConnection.class);
- mockBoxFolder = mock(BoxFolder.class);
-
- testSubject = new ListBoxFile() {
- @Override
- BoxFolder getFolder(String folderId) {
- return mockBoxFolder;
- }
- };
-
- testRunner = TestRunners.newTestRunner(testSubject);
- testRunner.setProperty(ListBoxFile.FOLDER_ID, folderId);
-
- BoxClientService boxClientService = mock(BoxClientService.class);
-
doReturn(boxClientService.toString()).when(boxClientService).getIdentifier();
-
doReturn(mockBoxAPIConnection).when(boxClientService).getBoxApiConnection();
-
- testRunner.addControllerService(boxClientService.getIdentifier(),
boxClientService);
- testRunner.enableControllerService(boxClientService);
- testRunner.setProperty(BoxClientService.BOX_CLIENT_SERVICE,
boxClientService.getIdentifier());
- }
-
- @Test
- void testOutputAsAttributesWhereTimestampIsModifiedTime() throws Exception
{
- // GIVEN
- String id = "id_1";
- String filename = "file_name_1";
- List<String> pathParts = Arrays.asList("path", "to", "file");
- Long size = 125L;
- Long createdTime = 123456L;
- Long modifiedTime = 123456L + 1L;
-
- // WHEN
- // THEN
- testOutputAsAttributes(
- id,
- filename,
- pathParts,
- size,
- createdTime,
- modifiedTime,
- modifiedTime,
- "/path/to/file"
- );
- }
-
- @Test
- void testOutputAsContent() throws Exception {
- // GIVEN
- String id = "id_1";
- String filename = "file_name_1";
- List<String> pathParts = Arrays.asList("path", "to", "file");
- Long size = 125L;
- Long createdTime = 123456L;
- Long modifiedTime = 123456L + 1L;
-
- addJsonRecordWriterFactory();
-
- mockFetchedFileList(id, filename, pathParts, size, createdTime,
modifiedTime);
-
- List<String> expectedContents = Arrays.asList(
- "[" +
- "{" +
- "\"box.id\":\"" + id + "\"," +
- "\"filename\":\"" + filename + "\"," +
- "\"path\":\"/path/to/file\"," +
- "\"box.size\":" + size + "," +
- "\"box.timestamp\":" + modifiedTime +
- "}" +
- "]");
-
- // WHEN
- testRunner.run();
-
- // THEN
- testRunner.assertContents(ListBoxFile.REL_SUCCESS, expectedContents);
- }
-
- private void addJsonRecordWriterFactory() throws InitializationException {
- RecordSetWriterFactory recordSetWriter = new JsonRecordSetWriter();
- testRunner.addControllerService("record_writer", recordSetWriter);
- testRunner.enableControllerService(recordSetWriter);
- testRunner.setProperty(ListBoxFile.RECORD_WRITER, "record_writer");
- }
-
- private void testOutputAsAttributes(
- String id,
- String filename,
- Collection<String> pathParts,
- Long size,
- Long createdTime,
- Long modifiedTime,
- Long expectedTimestamp,
- String expectedPath
- ) {
- // GIVEN
- mockFetchedFileList(id, filename, pathParts, size, createdTime,
modifiedTime);
-
- Map<String, String> inputFlowFileAttributes = new HashMap<>();
- inputFlowFileAttributes.put("box.id", id);
- inputFlowFileAttributes.put("filename", filename);
- inputFlowFileAttributes.put("path", expectedPath);
- inputFlowFileAttributes.put("box.size", "" + size);
- inputFlowFileAttributes.put("box.timestamp", "" + expectedTimestamp);
-
- HashSet<Map<String, String>> expectedAttributes = new
HashSet<>(Arrays.asList(inputFlowFileAttributes));
-
- // WHEN
- testRunner.run();
-
- // THEN
- testRunner.assertAttributes(ListBoxFile.REL_SUCCESS,
getCheckedAttributeNames(), expectedAttributes);
- }
-
- private Set<String> getCheckedAttributeNames() {
- Set<String> checkedAttributeNames =
Arrays.stream(BoxFlowFileAttribute.values())
- .map(BoxFlowFileAttribute::getName)
- .collect(Collectors.toSet());
-
- return checkedAttributeNames;
- }
-
- @Override
- public BoxFolder getMockBoxFolder() {
- return mockBoxFolder;
- }
-}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileIT.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileIT.java
new file mode 100644
index 0000000000..1eea15dbf1
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileIT.java
@@ -0,0 +1,120 @@
+/*
+ * 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
org.apache.nifi.processors.conflict.resolution.ConflictResolutionStrategy;
+import org.junit.jupiter.api.Test;
+
+/**
+ * See Javadoc {@link AbstractBoxFileIT} for instructions how to run this test.
+ */
+public class PutBoxFileIT extends AbstractBoxFileIT<PutBoxFile>{
+
+ @Override
+ public PutBoxFile createTestSubject() {
+ return new PutBoxFile();
+ }
+
+ @Test
+ void testUploadFile() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(FetchBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(FetchBoxFile.REL_FAILURE, 0);
+ }
+
+ @Test
+ void testSubfoldersAreCreated() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.SUBFOLDER_NAME, "sub1/sub2/sub3");
+ testRunner.setProperty(PutBoxFile.CREATE_SUBFOLDER, "true");
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(FetchBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(FetchBoxFile.REL_FAILURE, 0);
+ }
+
+ @Test
+ void testSubfolderExists() {
+ createFolder("sub1", mainFolderId);
+
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.SUBFOLDER_NAME, "sub1/sub2/sub3");
+ testRunner.setProperty(PutBoxFile.CREATE_SUBFOLDER, "true");
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(FetchBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(FetchBoxFile.REL_FAILURE, 0);
+ }
+
+ @Test
+ void testUploadExistingFileFailResolution() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 0);
+ testRunner.clearTransferState();
+ testRunner.enqueue(CHANGED_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 0);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 1);
+ }
+
+ @Test
+ void testUploadExistingFileIgnoreResolution() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.setProperty(PutBoxFile.CONFLICT_RESOLUTION,
ConflictResolutionStrategy.IGNORE.getValue());
+
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 0);
+ testRunner.clearTransferState();
+
+ testRunner.enqueue(CHANGED_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 0);
+ }
+
+ @Test
+ void testUploadExistingFileReplaceResolution() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, mainFolderId);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.setProperty(PutBoxFile.CONFLICT_RESOLUTION,
ConflictResolutionStrategy.REPLACE.getValue());
+
+ testRunner.enqueue(DEFAULT_FILE_CONTENT);
+ testRunner.run();
+
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 0);
+ testRunner.clearTransferState();
+
+ testRunner.enqueue(CHANGED_FILE_CONTENT);
+ testRunner.run();
+ testRunner.assertTransferCount(PutBoxFile.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(PutBoxFile.REL_FAILURE, 0);
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileTest.java
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileTest.java
new file mode 100644
index 0000000000..4cfc973c6c
--- /dev/null
+++
b/nifi-nar-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/PutBoxFileTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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 static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.box.sdk.BoxFile;
+import com.box.sdk.BoxFolder;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.provenance.ProvenanceEventType;
+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;
+
+@ExtendWith(MockitoExtension.class)
+public class PutBoxFileTest extends AbstractBoxFileTest {
+
+ public static final String SUBFOLDER1_ID = "aaaa";
+ public static final String SUBFOLDER2_ID = "bbbb";
+ @Mock
+ protected BoxFolder.Info mockSubfolder1Info;
+
+ @Mock
+ protected BoxFolder.Info mockSubfolder2Info;
+
+ @Mock
+ protected BoxFolder mockSubfolder1;
+
+ @Mock
+ protected BoxFolder mockSubfolder2;
+
+ private final Map<String, BoxFolder> mockBoxFolders = new HashMap<>();
+
+
+ @BeforeEach
+ void setUp() throws Exception {
+ initMockBoxFolderMap();
+ final PutBoxFile testSubject = new PutBoxFile() {
+ @Override
+ BoxFolder getFolder(String folderId) {
+ return mockBoxFolders.get(folderId);
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+ super.setUp();
+ }
+
+ private void initMockBoxFolderMap() {
+ mockBoxFolders.put(TEST_FOLDER_ID, mockBoxFolder);
+ mockBoxFolders.put(SUBFOLDER1_ID, mockSubfolder1);
+ mockBoxFolders.put(SUBFOLDER2_ID, mockSubfolder2);
+ }
+
+ @Test
+ void testUploadFilenameFromFlowFileAttribute() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, "${filename}");
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put(CoreAttributes.FILENAME.key(), TEST_FILENAME);
+ inputFlowFile.putAttributes(attributes);
+ inputFlowFile.setData(CONTENT.getBytes(UTF_8));
+
+ final BoxFile.Info mockUploadedFileInfo =
createFileInfo(TEST_FOLDER_NAME, MODIFIED_TIME);
+ when(mockBoxFolder.uploadFile(any(InputStream.class),
eq(TEST_FILENAME))).thenReturn(mockUploadedFileInfo);
+ when(mockBoxFolder.getInfo()).thenReturn(mockBoxFolderInfo);
+ when(mockBoxFolderInfo.getID()).thenReturn(TEST_FOLDER_ID);
+ when(mockBoxFolderInfo.getName()).thenReturn(TEST_FOLDER_NAME);
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(PutBoxFile.REL_SUCCESS, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(PutBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ assertOutFlowFileAttributes(ff0);
+ assertProvenanceEvent(ProvenanceEventType.SEND);
+ }
+
+ @Test
+ void testUploadFileExistingSubfolders() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(PutBoxFile.SUBFOLDER_NAME, "sub1/sub2");
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.setProperty(PutBoxFile.CREATE_SUBFOLDER, "true");
+
+
when(mockBoxFolder.getChildren("name")).thenReturn(singletonList(mockSubfolder1Info));
+
when(mockSubfolder1.getChildren("name")).thenReturn(singletonList(mockSubfolder2Info));
+
+ when(mockSubfolder1Info.getName()).thenReturn("sub1");
+ when(mockSubfolder2Info.getName()).thenReturn("sub2");
+ when(mockSubfolder1Info.getID()).thenReturn(SUBFOLDER1_ID);
+ when(mockSubfolder2Info.getID()).thenReturn(SUBFOLDER2_ID);
+ when(mockSubfolder1Info.getResource()).thenReturn(mockSubfolder1);
+ when(mockSubfolder2Info.getResource()).thenReturn(mockSubfolder2);
+ when(mockSubfolder2.getInfo()).thenReturn(mockSubfolder2Info);
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ inputFlowFile.setData(CONTENT.getBytes(UTF_8));
+
+ final BoxFile.Info mockUploadedFileInfo =
createFileInfo(TEST_FOLDER_NAME, MODIFIED_TIME, asList(mockBoxFolderInfo,
mockSubfolder1Info, mockSubfolder2Info));
+ when(mockSubfolder2.uploadFile(any(InputStream.class),
eq(TEST_FILENAME))).thenReturn(mockUploadedFileInfo);
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(PutBoxFile.REL_SUCCESS, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(PutBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ assertOutFlowFileAttributes(ff0, format("/%s/%s/%s", TEST_FOLDER_NAME,
"sub1", "sub2"));
+ assertProvenanceEvent(ProvenanceEventType.SEND);
+ }
+
+ @Test
+ void testUploadFileCreateSubfolders() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(PutBoxFile.SUBFOLDER_NAME, "new1/new2");
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+ testRunner.setProperty(PutBoxFile.CREATE_SUBFOLDER, "true");
+
+ when(mockBoxFolder.getChildren("name")).thenReturn(emptyList());
+ when(mockSubfolder1.getChildren("name")).thenReturn(emptyList());
+
+
when(mockBoxFolder.createFolder("new1")).thenReturn(mockSubfolder1Info);
+
when(mockSubfolder1.createFolder("new2")).thenReturn(mockSubfolder2Info);
+
+ when(mockSubfolder1Info.getResource()).thenReturn(mockSubfolder1);
+ when(mockSubfolder2Info.getResource()).thenReturn(mockSubfolder2);
+
+ when(mockSubfolder1Info.getName()).thenReturn("new1");
+ when(mockSubfolder1Info.getID()).thenReturn(SUBFOLDER1_ID);
+ when(mockSubfolder2Info.getName()).thenReturn("new2");
+ when(mockSubfolder2Info.getID()).thenReturn(SUBFOLDER2_ID);
+ when(mockSubfolder1.getID()).thenReturn(SUBFOLDER1_ID);
+
+ when(mockSubfolder2.getInfo()).thenReturn(mockSubfolder2Info);
+
+ when(mockBoxFolder.getID()).thenReturn(TEST_FOLDER_ID);
+ when(mockBoxFolderInfo.getID()).thenReturn(TEST_FOLDER_ID);
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ inputFlowFile.setData(CONTENT.getBytes(UTF_8));
+
+ final BoxFile.Info mockUploadedFileInfo =
createFileInfo(TEST_FOLDER_NAME, MODIFIED_TIME, asList(mockBoxFolderInfo,
mockSubfolder1Info, mockSubfolder2Info));
+ when(mockSubfolder2.uploadFile(any(InputStream.class),
eq(TEST_FILENAME))).thenReturn(mockUploadedFileInfo);
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(PutBoxFile.REL_SUCCESS, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(PutBoxFile.REL_SUCCESS);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ assertOutFlowFileAttributes(ff0, format("/%s/%s/%s", TEST_FOLDER_NAME,
"new1", "new2"));
+ assertProvenanceEvent(ProvenanceEventType.SEND);
+ verify(mockBoxFolder).createFolder("new1");
+ verify(mockSubfolder1).createFolder("new2");
+ }
+
+ @Test
+ void testUploadError() {
+ testRunner.setProperty(PutBoxFile.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(PutBoxFile.FILE_NAME, TEST_FILENAME);
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ inputFlowFile.setData(CONTENT.getBytes(UTF_8));
+
+ when(mockBoxFolderInfo.getName()).thenReturn(TEST_FOLDER_NAME);
+ when(mockBoxFolder.getInfo()).thenReturn(mockBoxFolderInfo);
+ when(mockBoxFolder.uploadFile(any(InputStream.class),
eq(TEST_FILENAME))).thenThrow(new RuntimeException("Upload error"));
+
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertAllFlowFilesTransferred(PutBoxFile.REL_FAILURE, 1);
+ final List<MockFlowFile> flowFiles =
testRunner.getFlowFilesForRelationship(PutBoxFile.REL_FAILURE);
+ final MockFlowFile ff0 = flowFiles.get(0);
+ ff0.assertAttributeEquals(BoxFileAttributes.ERROR_MESSAGE, "Upload
error");
+ assertNoProvenanceEvent();
+ }
+}