diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cd78684
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+target
+.idea
+.project
+.settings
+.classpath
+.DS_Store
+.metadata
+.recommenders
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..5b03111
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+# Copyright 2025 Snowflake Inc.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed 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.
+
+FROM maven:3.9.9-eclipse-temurin-21 as builder
+
+COPY flow-diff /flow-diff
+RUN mvn -f /flow-diff/pom.xml clean package
+
+FROM eclipse-temurin:21-jre-noble
+
+COPY entrypoint.sh /entrypoint.sh
+COPY --from=builder /flow-diff/target/flow-diff.jar /flow-diff.jar
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..66a27ec
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,177 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b4ab80e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+#  Snowflake Flow Diff for Apache NiFi
+
+This action is brought to you by [Snowflake](https://www.snowflake.com/), don't hesitate to visit our website and reach out to us if you have questions about this action.
+
+## Usage
+
+When using the GitHub Flow Registry Client in NiFi to version control your flows, add the below file `.github/workflows/flowdiff.yml` to the repository into which flow definitions are versioned.
+
+Whenever a pull request is opened, reopened or when a new commit is pushed to an existing pull request, this workflow will be triggered and will compare the modified flow in the pull request and will
+automatically comment the pull request with a human readable description of the changes included in the pull request.
+
+```yaml
+name: Snowflake Flow Diff on Pull Requests
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+
+jobs:
+ execute_flow_diff:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ name: Executing Flow Diff
+ steps:
+ # checking out the code of the pull request (merge commit - if the PR is mergeable)
+ - name: Checkout PR code
+ uses: actions/checkout@v4
+ with:
+ path: submitted-changes
+
+ # getting the path of the flow definition that changed (only one expected for now)
+ - id: files
+ uses: Ana06/get-changed-files@v1.2
+
+ # checking out the code without the change of the PR
+ - name: Checkout original code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ path: original-code
+ - run: cd original-code && git checkout HEAD^
+
+ # Running the diff
+ - name: Snowflake Flow Diff
+ uses: snowflake-labs/snowflake-flow-diff@v0
+ id: flowdiff
+ with:
+ flowA: 'original-code/${{ steps.files.outputs.all }}'
+ flowB: 'submitted-changes/${{ steps.files.outputs.all }}'
+```
+
+## Example
+
+The GitHub Action will automatically publish a comment on the pull request with a comprehensive description of the changes between the flows of the two branches.
+Here is an example of what the comment could look like:
+
+```markdown
+### Executing Snowflake Flow Diff for flow: `MyExample`
+
+- The destination of a connection has changed from `UpdateAttribute` to `InvokeHTTP`
+- A self-loop connection `[success]` has been added on `UpdateAttribute`
+- A connection `[success]` from `My Generate FlowFile Processor` to `UpdateAttribute` has been added
+- A Processor has been renamed from `GenerateFlowFile` to `My Generate FlowFile Processor`
+- In processor named `GenerateFlowFile`, the Scheduling Strategy changed from `TIMER_DRIVEN` to `CRON_DRIVEN`
+- A Parameter Context named `Test Parameter Context` has been added
+- The parameter context `Test Parameter Context` with parameters `{addedParam=newValue}` has been added to the process group `TestingFlowDiff`
+- A Processor named `UpdateAttribute` has been removed
+- The bundle `org.apache.nifi:nifi-standard-nar` has been changed from version `2.1.0` to version `2.2.0`
+- In processor `GenerateFlowFile`, the Run Schedule changed from `1 min` to `* * * * * ?`
+- A Parameter Context named `Another one to delete` has been added
+- A Processor `UpdateAttribute` has been added with the below configuration
+ - `Store State` = `Do not store state`
+ - `canonical-value-lookup-cache-size` = `100`
+```
diff --git a/action.yml b/action.yml
new file mode 100644
index 0000000..e96b3cb
--- /dev/null
+++ b/action.yml
@@ -0,0 +1,49 @@
+# Copyright 2025 Snowflake Inc.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed 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.
+
+name: 'Snowflake Flow Diff'
+description: 'Compare differences in Apache NiFi Flow Definitions'
+author: 'Snowflake Inc.'
+branding:
+ icon: git-pull-request
+ color: blue
+inputs:
+ flowA:
+ description: 'Original Flow'
+ required: true
+ flowB:
+ description: 'New Version of the Flow'
+ required: true
+ token:
+ description: 'GITHUB_TOKEN or a repository-scoped PAT'
+ default: ${{ github.token }}
+ required: true
+ repository:
+ description: 'The full name of the repository in which to create or update a comment'
+ default: ${{ github.repository }}
+ required: true
+ issuenumber:
+ description: 'The number of the issue or pull request in which to create a comment'
+ required: true
+ default: ${{ github.event.number }}
+runs:
+ using: 'docker'
+ image: 'Dockerfile'
+ args:
+ - ${{ inputs.flowA }}
+ - ${{ inputs.flowB }}
+ - ${{ inputs.token }}
+ - ${{ inputs.repository }}
+ - ${{ inputs.issuenumber }}
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100755
index 0000000..31737ef
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,26 @@
+#!/bin/sh -l
+
+# Copyright 2025 Snowflake Inc.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed 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.
+
+java -jar /flow-diff.jar $1 $2 >> /github/workspace/diff.txt
+
+OUTPUT=$(cat /github/workspace/diff.txt | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
+
+curl -X POST \
+ -H "Authorization: Token $3" \
+ -H "Accept: application/vnd.github+json" \
+ https://api.github.com/repos/$4/issues/$5/comments \
+ -d "{\"body\":\"$OUTPUT\"}"
diff --git a/flow-diff/pom.xml b/flow-diff/pom.xml
new file mode 100644
index 0000000..d56b5ab
--- /dev/null
+++ b/flow-diff/pom.xml
@@ -0,0 +1,136 @@
+
+ 4.0.0
+
+ com.snowflake.openflow
+ flow-diff
+ 0.0.1
+
+ Snowflake Flow Diff GitHub Action
+
+ GitHub Action so that whenever a pull request is opened, reopened or when a new commit is
+ pushed to an existing pull request, a workflow will be triggered and will compare the
+ modified flow in the pull request and will automatically comment the pull request with a
+ human-readable description of the changes included in the pull request.
+
+ https://github.com/snowflake-labs/snowflake-flow-diff
+ 2025
+
+ Snowflake Inc.
+ https://www.snowflake.com/
+
+
+
+ Apache-2.0
+ https://www.apache.org/licenses/LICENSE-2.0
+
+
+
+
+ UTF-8
+ UTF-8
+ 21
+ 2.0.0
+ 2.2.0
+ 2.0.16
+ 2.18.2
+ 5.11.4
+
+
+
+
+ org.apache.nifi
+ nifi-api
+ ${nifi-api.version}
+
+
+ org.apache.nifi
+ nifi-framework-core-api
+ ${nifi-framework.version}
+
+
+ org.apache.nifi
+ nifi-site-to-site-client
+
+
+ org.apache.nifi
+ nifi-expression-language
+
+
+ org.apache.nifi
+ nifi-framework-authorization
+
+
+ org.apache.nifi
+ c2-protocol-component-api
+
+
+ io.swagger.core.v3
+ swagger-annotations
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+
+
+
+
+ org.apache.nifi.registry
+ nifi-registry-flow-diff
+ ${nifi-framework.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ org.slf4j
+ slf4j-nop
+ ${slf4j.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+ test
+
+
+
+
+ ${project.artifactId}
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ package
+
+ shade
+
+
+
+
+ com.snowflake.openflow.FlowDiff
+
+
+ false
+
+
+
+
+
+
+
diff --git a/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java
new file mode 100644
index 0000000..a167d4b
--- /dev/null
+++ b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2025 Snowflake Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed 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 com.snowflake.openflow;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.ComponentType;
+import org.apache.nifi.flow.ConnectableComponent;
+import org.apache.nifi.flow.VersionedComponent;
+import org.apache.nifi.flow.VersionedConnection;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+import org.apache.nifi.registry.flow.FlowSnapshotContainer;
+import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
+import org.apache.nifi.registry.flow.diff.ConciseEvolvingDifferenceDescriptor;
+import org.apache.nifi.registry.flow.diff.FlowComparator;
+import org.apache.nifi.registry.flow.diff.FlowComparatorVersionedStrategy;
+import org.apache.nifi.registry.flow.diff.FlowDifference;
+import org.apache.nifi.registry.flow.diff.StandardComparableDataFlow;
+import org.apache.nifi.registry.flow.diff.StandardFlowComparator;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
+
+public class FlowDiff {
+
+ private static String flowName;
+ private static Map parameterContexts;
+
+ public static void main(String[] args) throws IOException {
+
+ String pathA = args[0];
+ String pathB = args[1];
+
+ final Set diffs = getDiff(pathA, pathB);
+
+ System.out.println("> [!NOTE]");
+ System.out.println("> This GitHub Action is created and maintained by [Snowflake](https://www.snowflake.com/).");
+ System.out.println("### Executing Datavolo Flow Diff for flow: `" + flowName + "`");
+
+ for(FlowDifference diff : diffs) {
+
+ switch (diff.getDifferenceType()) {
+ case COMPONENT_ADDED: {
+ if (diff.getComponentB().getComponentType().equals(ComponentType.FUNNEL)) {
+ System.out.println("- A Funnel has been added");
+ } else if (diff.getComponentB().getComponentType().equals(ComponentType.CONNECTION)) {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentB();
+ if (connection.getSource().getId().equals(connection.getDestination().getId())) {
+ System.out.println("- A self-loop connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` has been added on `" + connection.getSource().getName() + "`");
+ } else {
+ System.out.println("- A connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been added");
+ }
+ } else if (diff.getComponentB().getComponentType().equals(ComponentType.PROCESSOR)) {
+ final VersionedProcessor proc = (VersionedProcessor) diff.getComponentB();
+ System.out.println("- A Processor"
+ + (isEmpty(diff.getComponentB().getName()) ? "" : " `" + diff.getComponentB().getName() + "`")
+ + " has been added with the configuration [" + printProcessorConf(proc) + "] and the below properties:");
+ printProcessorProperties(proc);
+ } else if (diff.getComponentB().getComponentType().equals(ComponentType.CONTROLLER_SERVICE)) {
+ final VersionedControllerService cs = (VersionedControllerService) diff.getComponentB();
+ System.out.println("- A Controller Service"
+ + (isEmpty(diff.getComponentB().getName()) ? "" : " `" + diff.getComponentB().getName() + "`")
+ + " has been added with the below properties:");
+ printControllerProperties(cs);
+ } else {
+ System.out.println("- A " + diff.getComponentB().getComponentType().getTypeName()
+ + (isEmpty(diff.getComponentB().getName()) ? "" : " named `" + diff.getComponentB().getName() + "`")
+ + " has been added");
+ }
+ break;
+ }
+ case COMPONENT_REMOVED: {
+ if (diff.getComponentA().getComponentType().equals(ComponentType.FUNNEL)) {
+ System.out.println("- A Funnel has been removed");
+ } else {
+ System.out.println("- A " + diff.getComponentA().getComponentType().getTypeName()
+ + (isEmpty(diff.getComponentA().getName()) ? "" : " named `" + diff.getComponentA().getName() + "`")
+ + " has been removed");
+ }
+ break;
+ }
+ case DESTINATION_CHANGED: {
+ System.out.println("- The destination of a connection has changed from `" + ((ConnectableComponent) diff.getValueA()).getName()
+ + "` to `" + ((ConnectableComponent) diff.getValueB()).getName() + "`");
+ break;
+ }
+ case PROPERTY_CHANGED: {
+ System.out.println("- In the " + diff.getComponentA().getComponentType().getTypeName()
+ + " named `" + diff.getComponentA().getName() + "`, the value of the property "
+ + "`" + diff.getFieldName().get() + "` changed from `" + diff.getValueA()
+ + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case CONCURRENT_TASKS_CHANGED: {
+ System.out.println("- In processor `" + diff.getComponentA().getName() + "`, the number of concurrent tasks has been "
+ + ((int) diff.getValueA() > (int) diff.getValueB() ? "decreased" : "increased")
+ + " from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED: {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The data size backpressure threshold for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case BACKPRESSURE_OBJECT_THRESHOLD_CHANGED: {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The flowfile number backpressure threshold for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case BULLETIN_LEVEL_CHANGED: {
+ System.out.println("- In " + diff.getComponentA().getComponentType() + " named `" + diff.getComponentA().getName()
+ + "`, the bulletin level has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case RUN_DURATION_CHANGED: {
+ final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA();
+ System.out.println("- In processor `" + processor.getName()
+ + "`, the Run Duration changed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case RUN_SCHEDULE_CHANGED: {
+ final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA();
+ System.out.println("- In processor `" + processor.getName()
+ + "`, the Run Schedule changed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case AUTO_TERMINATED_RELATIONSHIPS_CHANGED: {
+ final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA();
+ System.out.println("- In processor `" + processor.getName()
+ + "`, the list of auto-terminated relationships changed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case LOAD_BALANCE_STRATEGY_CHANGED: {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The load balancing strategy for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case LOAD_BALANCE_COMPRESSION_CHANGED: {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The load balancing compression for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case FLOWFILE_EXPIRATION_CHANGED: {
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The flow file expiration for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case PENALTY_DURATION_CHANGED: {
+ final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA();
+ System.out.println("- In processor `" + processor.getName()
+ + "`, the penalty duration changed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case PARAMETER_CONTEXT_CHANGED: {
+ final VersionedProcessGroup pg = (VersionedProcessGroup) diff.getComponentB();
+ System.out.println("- The parameter context `" + pg.getParameterContextName() + "` with parameters `"
+ + printParameterContext(parameterContexts.get(pg.getParameterContextName()))
+ + "` has been added to the process group `" + pg.getName() + "`");
+ break;
+ }
+ case POSITION_CHANGED: {
+ System.out.println("- A " + diff.getComponentA().getComponentType()
+ + (isEmpty(diff.getComponentA().getName()) ? "" : " named `" + diff.getComponentA().getName() + "`")
+ + " has been moved to another position");
+ break;
+ }
+ case SCHEDULING_STRATEGY_CHANGED: {
+ final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA();
+ System.out.println("- In processor named `" + processor.getName()
+ + "`, the Scheduling Strategy changed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case BUNDLE_CHANGED:
+ Bundle before = (Bundle) diff.getValueA();
+ Bundle after = (Bundle) diff.getValueB();
+ System.out.println("- The bundle `"
+ + before.getGroup() + ":" + before.getArtifact()
+ + "` has been changed from version "
+ + "`" + before.getVersion() + "` to version `" + after.getVersion() + "`");
+ break;
+ case NAME_CHANGED: {
+ System.out.println("- A "
+ + diff.getComponentA().getComponentType()
+ + " has been renamed from "
+ + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case PROPERTY_ADDED: {
+ final String propKey = (String) diff.getValueB();
+ String propValue = null;
+ if (diff.getComponentB() instanceof VersionedProcessor) {
+ if (((VersionedProcessor) diff.getComponentB()).getPropertyDescriptors().get(propKey).isSensitive()) {
+ propValue = "";
+ } else {
+ propValue = ((VersionedProcessor) diff.getComponentB()).getProperties().get(propKey);
+ }
+ }
+ if (diff.getComponentB() instanceof VersionedControllerService) {
+ if (((VersionedControllerService) diff.getComponentB()).getPropertyDescriptors().get(propKey).isSensitive()) {
+ propValue = "";
+ } else {
+ propValue = ((VersionedControllerService) diff.getComponentB()).getProperties().get(propKey);
+ }
+ }
+ System.out.println("- In " + diff.getComponentA().getComponentType()
+ + " named `" + diff.getComponentA().getName() + "`, a property has been added: "
+ + "`" + propKey + "` = `" + propValue + "`");
+ break;
+ }
+ case PROPERTY_PARAMETERIZED: {
+ final String propKey = diff.getFieldName().get();
+ String propValue = null;
+ if (diff.getComponentB() instanceof VersionedProcessor) {
+ propValue = ((VersionedProcessor) diff.getComponentB()).getProperties().get(propKey);
+ }
+ if (diff.getComponentB() instanceof VersionedControllerService) {
+ propValue = ((VersionedControllerService) diff.getComponentB()).getProperties().get(propKey);
+ }
+ System.out.println("- In " + diff.getComponentA().getComponentType()
+ + " named `" + diff.getComponentA().getName() + "`, a property is now referencing a parameter: "
+ + "`" + propKey + "` = `" + propValue + "`");
+ break;
+ }
+ case PROPERTY_PARAMETERIZATION_REMOVED: {
+ final String propKey = diff.getFieldName().get();
+ System.out.println("- In " + diff.getComponentA().getComponentType()
+ + " named `" + diff.getComponentA().getName()
+ + "`, the property `" + propKey + "` is no longer referencing a parameter");
+ break;
+ }
+ case SCHEDULED_STATE_CHANGED: {
+ System.out.println("- In the " + diff.getComponentA().getComponentType().getTypeName()
+ + " named `" + diff.getComponentA().getName()
+ + "`, the Schedule State changed from `"
+ + diff.getValueA() + "` to `" + diff.getValueB() + "`");
+ break;
+ }
+ case PARAMETER_ADDED: {
+ final String paramKey = diff.getFieldName().get();
+ final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentB();
+ final VersionedParameter param = pc.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get();
+ System.out.println("- In the Parameter Context `" + pc.getName() + "` a parameter has been added: `"
+ + paramKey + "` = `" + (param.isSensitive() ? "" : param.getValue()) + "`");
+ break;
+ }
+ case PARAMETER_REMOVED: {
+ final String paramKey = diff.getFieldName().get();
+ final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentB();
+ System.out.println("- In the Parameter Context `"+ pc.getName() + "` the parameter `" + paramKey + "` has been removed");
+ break;
+ }
+ case PROPERTY_REMOVED: {
+ final String propKey = diff.getFieldName().get();
+ System.out.println("- In "
+ + diff.getComponentA().getComponentType()
+ + " named `" + diff.getComponentA().getName()
+ + "`, the property `" + propKey + "` has been removed");
+ break;
+ }
+ case PARAMETER_VALUE_CHANGED: {
+ final String paramKey = diff.getFieldName().get();
+ final VersionedParameterContext pcBefore = (VersionedParameterContext) diff.getComponentA();
+ final VersionedParameterContext pcAfter = (VersionedParameterContext) diff.getComponentB();
+ final VersionedParameter paramBefore = pcBefore.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get();
+ final VersionedParameter paramAfter = pcAfter.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get();
+ System.out.println("- In the Parameter Context `" + pcAfter.getName()
+ + "`, the value of the parameter `" + paramKey + "` has changed from `"
+ + (paramBefore.isSensitive() ? "" : paramBefore.getValue()) + "`"
+ + " to `"
+ + (paramAfter.isSensitive() ? "" : paramAfter.getValue()) + "`");
+ break;
+ }
+ case INHERITED_CONTEXTS_CHANGED:
+ final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentA();
+ System.out.println("- In the Parameter Context `" + pc.getName()
+ + "`, the list of inherited parameter contexts changed from `"
+ + diff.getValueA() + "`" + " to `" + diff.getValueB() + "`");
+ break;
+ case BENDPOINTS_CHANGED:
+ final VersionedConnection connection = (VersionedConnection) diff.getComponentA();
+ System.out.println("- The bending points for the connection `"
+ + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName())
+ + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName()
+ + "` have been changed");
+ break;
+ default:
+ System.out.println("- " + diff.getDescription() + " (" + diff.getDifferenceType() + ")");
+ System.out.println(" - " + diff.getValueA());
+ System.out.println(" - " + diff.getValueB());
+ System.out.println(" - " + diff.getComponentA());
+ System.out.println(" - " + diff.getComponentB());
+ System.out.println(" - " + diff.getFieldName());
+ break;
+ }
+ }
+ }
+
+ public static Set getDiff(final String pathA, final String pathB) throws IOException {
+ final ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ final JsonFactory factory = new JsonFactory(objectMapper);
+ final FlowSnapshotContainer snapshotA = getFlowContainer(pathA, factory);
+ final FlowSnapshotContainer snapshotB = getFlowContainer(pathB, factory);
+
+ // identifier is null for parameter contexts, and we know that names are unique so setting name as id
+ snapshotA.getFlowSnapshot().getParameterContexts().values().forEach(pc -> pc.setIdentifier(pc.getName()));
+ snapshotB.getFlowSnapshot().getParameterContexts().values().forEach(pc -> pc.setIdentifier(pc.getName()));
+
+ final FlowComparator flowComparator = new StandardFlowComparator(
+ new StandardComparableDataFlow(
+ "Flow A",
+ snapshotA.getFlowSnapshot().getFlowContents(),
+ null,
+ null,
+ null,
+ new HashSet<>(snapshotA.getFlowSnapshot().getParameterContexts().values()),
+ null,
+ null
+ ),
+ new StandardComparableDataFlow(
+ "Flow B",
+ snapshotB.getFlowSnapshot().getFlowContents(),
+ null,
+ null,
+ null,
+ new HashSet<>(snapshotB.getFlowSnapshot().getParameterContexts().values()),
+ null,
+ null
+ ),
+ Collections.emptySet(),
+ new ConciseEvolvingDifferenceDescriptor(),
+ Function.identity(),
+ VersionedComponent::getIdentifier,
+ FlowComparatorVersionedStrategy.DEEP
+ );
+
+ flowName = snapshotA.getFlowSnapshot().getFlow().getName();
+ parameterContexts = snapshotB.getFlowSnapshot().getParameterContexts();
+
+ final SortedSet sortedDiffs = new TreeSet(new Comparator() {
+ @Override
+ public int compare(FlowDifference o1, FlowDifference o2) {
+ String id1 = o1.getComponentA() == null ? String.valueOf(o1.hashCode()) : o1.getComponentA().getInstanceIdentifier() + o1.hashCode();
+ String id2 = o2.getComponentA() == null ? String.valueOf(o2.hashCode()) : o2.getComponentA().getInstanceIdentifier() + o2.hashCode();
+ return (id1 == null ? String.valueOf(o1.hashCode()) : id1).compareTo(id2 == null ? String.valueOf(o2.hashCode()) : id2);
+ }
+ });
+ sortedDiffs.addAll(flowComparator.compare().getDifferences());
+
+ return sortedDiffs;
+ }
+
+ static FlowSnapshotContainer getFlowContainer(final String path, final JsonFactory factory) throws IOException {
+ final File snapshotFile = new File(path);
+ try (final JsonParser parser = factory.createParser(snapshotFile)) {
+ final RegisteredFlowSnapshot snapshot = parser.readValueAs(RegisteredFlowSnapshot.class);
+ return new FlowSnapshotContainer(snapshot);
+ }
+ }
+
+ static String printParameterContext(final VersionedParameterContext pc) {
+ final Map parameters = new HashMap<>();
+ for (VersionedParameter p : pc.getParameters()) {
+ if (p.isSensitive()) {
+ parameters.put(p.getName(), "");
+ } else {
+ parameters.put(p.getName(), p.getValue());
+ }
+ }
+ return parameters.toString();
+ }
+
+ static void printProcessorProperties(final VersionedProcessor proc) {
+ for (String key : proc.getProperties().keySet()) {
+ System.out.println(" - `" + key + "` = `" + proc.getProperties().get(key) + "`");
+ }
+ }
+
+ static String printProcessorConf(final VersionedProcessor proc) {
+ return "`" + proc.getExecutionNode() + "` nodes, `" + proc.getConcurrentlySchedulableTaskCount() + "` concurrent tasks, `"
+ + proc.getRunDurationMillis() + "ms` run duration, `" + proc.getBulletinLevel() + "` bulletin level, `"
+ + proc.getSchedulingStrategy() + "` (`" + proc.getSchedulingPeriod() + "`), `"
+ + proc.getPenaltyDuration() + "` penalty duration, `" + proc.getYieldDuration() + "` yield duration";
+ }
+
+ static void printControllerProperties(final VersionedControllerService cs) {
+ for (String key : cs.getProperties().keySet()) {
+ System.out.println(" - `" + key + "` = `" + cs.getProperties().get(key) + "`");
+ }
+ }
+
+ static boolean isEmpty(final String string) {
+ return string == null || string.isEmpty();
+ }
+}
diff --git a/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java
new file mode 100644
index 0000000..8867a22
--- /dev/null
+++ b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2025 Snowflake Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed 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 com.snowflake.openflow;
+
+import org.apache.nifi.registry.flow.diff.DifferenceType;
+import org.apache.nifi.registry.flow.diff.FlowDifference;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FlowDiffTest {
+
+ @Test
+ void testDiffV1V2() throws IOException {
+ String flowV1 = "src/test/resources/flow_v1_initial.json";
+ String flowV2 = "src/test/resources/flow_v2_added_component.json";
+ Set diffs = FlowDiff.getDiff(flowV1, flowV2);
+ assertEquals(diffs.size(), 3);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.POSITION_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.COMPONENT_ADDED)));
+ }
+
+ @Test
+ void testDiffV2V3() throws IOException {
+ String flowV2 = "src/test/resources/flow_v2_added_component.json";
+ String flowV3 = "src/test/resources/flow_v3_config_changes.json";
+ Set diffs = FlowDiff.getDiff(flowV2, flowV3);
+ assertEquals(diffs.size(), 13);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.RUN_SCHEDULE_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.FLOWFILE_EXPIRATION_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BULLETIN_LEVEL_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.CONCURRENT_TASKS_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PENALTY_DURATION_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_CONTEXT_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BACKPRESSURE_OBJECT_THRESHOLD_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.AUTO_TERMINATED_RELATIONSHIPS_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.LOAD_BALANCE_STRATEGY_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.LOAD_BALANCE_COMPRESSION_CHANGED)));
+ }
+
+ @Test
+ void testDiffV3V4() throws IOException {
+ String flowV3 = "src/test/resources/flow_v3_config_changes.json";
+ String flowV4 = "src/test/resources/flow_v4_parameters.json";
+ Set diffs = FlowDiff.getDiff(flowV3, flowV4);
+ assertEquals(diffs.size(), 14);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.INHERITED_CONTEXTS_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_ADDED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.SCHEDULING_STRATEGY_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_PARAMETERIZED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.NAME_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BUNDLE_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.COMPONENT_REMOVED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.RUN_SCHEDULE_CHANGED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_REMOVED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.DESTINATION_CHANGED)));
+ }
+
+ @Test
+ void testDiffV4V5() throws IOException {
+ String flowV4 = "src/test/resources/flow_v4_parameters.json";
+ String flowV5 = "src/test/resources/flow_v5_property_parameter.json";
+ Set diffs = FlowDiff.getDiff(flowV4, flowV5);
+ assertEquals(diffs.size(), 7);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_PARAMETERIZATION_REMOVED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.SCHEDULED_STATE_CHANGED)));
+ }
+
+ @Test
+ void testDiffV3V5() throws IOException {
+ String flowV3 = "src/test/resources/flow_v3_config_changes.json";
+ String flowV5 = "src/test/resources/flow_v5_property_parameter.json";
+ Set diffs = FlowDiff.getDiff(flowV3, flowV5);
+ assertEquals(diffs.size(), 13);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_ADDED)));
+ }
+
+ @Test
+ void testDiffV5V6() throws IOException {
+ String flowV5 = "src/test/resources/flow_v5_property_parameter.json";
+ String flowV6 = "src/test/resources/flow_v6_parameter_value.json";
+ Set diffs = FlowDiff.getDiff(flowV5, flowV6);
+ assertEquals(diffs.size(), 3);
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_REMOVED)));
+ assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_VALUE_CHANGED)));
+ }
+}
diff --git a/flow-diff/src/test/resources/flow_v1_initial.json b/flow-diff/src/test/resources/flow_v1_initial.json
new file mode 100644
index 0000000..9647d47
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v1_initial.json
@@ -0,0 +1,233 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "GenerateFlowFile",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "success" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -288.0,
+ "y" : -24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "GenerateFlowFile",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "Batch Size" : "1",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "1 min",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : { },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file
diff --git a/flow-diff/src/test/resources/flow_v2_added_component.json b/flow-diff/src/test/resources/flow_v2_added_component.json
new file mode 100644
index 0000000..e52cb9c
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v2_added_component.json
@@ -0,0 +1,328 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "GenerateFlowFile",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "GenerateFlowFile",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "success" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -504.0,
+ "y" : 16.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "GenerateFlowFile",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "Batch Size" : "1",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "1 min",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ "success" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -16.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : { },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file
diff --git a/flow-diff/src/test/resources/flow_v3_config_changes.json b/flow-diff/src/test/resources/flow_v3_config_changes.json
new file mode 100644
index 0000000..4f6fba2
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v3_config_changes.json
@@ -0,0 +1,378 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ {
+ "x" : 453.0,
+ "y" : 63.0
+ }, {
+ "x" : 453.0,
+ "y" : 113.0
+ } ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 MB",
+ "backPressureObjectThreshold" : 1000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "100 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT",
+ "loadBalanceStrategy" : "ROUND_ROBIN",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "GenerateFlowFile",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "GenerateFlowFile",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "parameterContextName" : "Test Parameter Context",
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "success" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -504.0,
+ "y" : 16.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "GenerateFlowFile",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "Batch Size" : "1",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "1 min",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "ERROR",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.03-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 2,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "10 sec",
+ "position" : {
+ "x" : -16.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "10 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : {
+ "Test Parameter Context" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ ],
+ "name" : "Test Parameter Context",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "test",
+ "provided" : false,
+ "sensitive" : false,
+ "value" : "test"
+ } ]
+ }
+ },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file
diff --git a/flow-diff/src/test/resources/flow_v4_parameters.json b/flow-diff/src/test/resources/flow_v4_parameters.json
new file mode 100644
index 0000000..7c2ed14
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v4_parameters.json
@@ -0,0 +1,638 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ {
+ "x" : 453.0,
+ "y" : 63.0
+ }, {
+ "x" : 453.0,
+ "y" : 113.0
+ } ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 MB",
+ "backPressureObjectThreshold" : 1000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "100 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT",
+ "loadBalanceStrategy" : "ROUND_ROBIN",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "name" : "InvokeHTTP",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "parameterContextName" : "Test Parameter Context",
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "InvokeHTTP",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -496.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Request Content-Encoding" : "DISABLED",
+ "Request Multipart Form-Data Filename Enabled" : "true",
+ "Request Chunked Transfer-Encoding Enabled" : "false",
+ "HTTP/2 Disabled" : "False",
+ "Connection Timeout" : "5 secs",
+ "Response Cookie Strategy" : "DISABLED",
+ "Socket Read Timeout" : "15 secs",
+ "Socket Idle Connections" : "5",
+ "Request Body Enabled" : "true",
+ "HTTP URL" : "${url}",
+ "Socket Idle Timeout" : "5 mins",
+ "Response Redirects Enabled" : "True",
+ "Socket Write Timeout" : "15 secs",
+ "Response FlowFile Naming Strategy" : "RANDOM",
+ "Response Cache Enabled" : "false",
+ "Request Date Header Enabled" : "True",
+ "Request Failure Penalization Enabled" : "false",
+ "Response Body Attribute Size" : "256",
+ "Response Generation Required" : "false",
+ "Response Header Request Attributes Enabled" : "false",
+ "HTTP Method" : "GET",
+ "Request Content-Type" : "${mime.type}",
+ "Request Digest Authentication Enabled" : "false",
+ "Response Cache Size" : "10MB",
+ "Response Body Ignored" : "false"
+ },
+ "propertyDescriptors" : {
+ "Request Content-Encoding" : {
+ "displayName" : "Request Content-Encoding",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Encoding",
+ "sensitive" : false
+ },
+ "proxy-configuration-service" : {
+ "displayName" : "Proxy Configuration Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "proxy-configuration-service",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Filename Enabled" : {
+ "displayName" : "Request Multipart Form-Data Filename Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Filename Enabled",
+ "sensitive" : false
+ },
+ "Request Chunked Transfer-Encoding Enabled" : {
+ "displayName" : "Request Chunked Transfer-Encoding Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Chunked Transfer-Encoding Enabled",
+ "sensitive" : false
+ },
+ "HTTP/2 Disabled" : {
+ "displayName" : "HTTP/2 Disabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP/2 Disabled",
+ "sensitive" : false
+ },
+ "Connection Timeout" : {
+ "displayName" : "Connection Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Connection Timeout",
+ "sensitive" : false
+ },
+ "Response Cookie Strategy" : {
+ "displayName" : "Response Cookie Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cookie Strategy",
+ "sensitive" : false
+ },
+ "Request Password" : {
+ "displayName" : "Request Password",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Password",
+ "sensitive" : true
+ },
+ "Socket Read Timeout" : {
+ "displayName" : "Socket Read Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Read Timeout",
+ "sensitive" : false
+ },
+ "Socket Idle Connections" : {
+ "displayName" : "Socket Idle Connections",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Connections",
+ "sensitive" : false
+ },
+ "Request Body Enabled" : {
+ "displayName" : "Request Body Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Body Enabled",
+ "sensitive" : false
+ },
+ "HTTP URL" : {
+ "displayName" : "HTTP URL",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP URL",
+ "sensitive" : false
+ },
+ "Request OAuth2 Access Token Provider" : {
+ "displayName" : "Request OAuth2 Access Token Provider",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "Request OAuth2 Access Token Provider",
+ "sensitive" : false
+ },
+ "Socket Idle Timeout" : {
+ "displayName" : "Socket Idle Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Timeout",
+ "sensitive" : false
+ },
+ "Response Redirects Enabled" : {
+ "displayName" : "Response Redirects Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Redirects Enabled",
+ "sensitive" : false
+ },
+ "Socket Write Timeout" : {
+ "displayName" : "Socket Write Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Write Timeout",
+ "sensitive" : false
+ },
+ "Request Header Attributes Pattern" : {
+ "displayName" : "Request Header Attributes Pattern",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Header Attributes Pattern",
+ "sensitive" : false
+ },
+ "Response FlowFile Naming Strategy" : {
+ "displayName" : "Response FlowFile Naming Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response FlowFile Naming Strategy",
+ "sensitive" : false
+ },
+ "Response Cache Enabled" : {
+ "displayName" : "Response Cache Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Enabled",
+ "sensitive" : false
+ },
+ "Request Date Header Enabled" : {
+ "displayName" : "Request Date Header Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Date Header Enabled",
+ "sensitive" : false
+ },
+ "Request Failure Penalization Enabled" : {
+ "displayName" : "Request Failure Penalization Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Failure Penalization Enabled",
+ "sensitive" : false
+ },
+ "Response Body Attribute Size" : {
+ "displayName" : "Response Body Attribute Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Size",
+ "sensitive" : false
+ },
+ "SSL Context Service" : {
+ "displayName" : "SSL Context Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "SSL Context Service",
+ "sensitive" : false
+ },
+ "Response Generation Required" : {
+ "displayName" : "Response Generation Required",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Generation Required",
+ "sensitive" : false
+ },
+ "Request User-Agent" : {
+ "displayName" : "Request User-Agent",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request User-Agent",
+ "sensitive" : false
+ },
+ "Response Header Request Attributes Enabled" : {
+ "displayName" : "Response Header Request Attributes Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Header Request Attributes Enabled",
+ "sensitive" : false
+ },
+ "HTTP Method" : {
+ "displayName" : "HTTP Method",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP Method",
+ "sensitive" : false
+ },
+ "Request Username" : {
+ "displayName" : "Request Username",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Username",
+ "sensitive" : false
+ },
+ "Request Content-Type" : {
+ "displayName" : "Request Content-Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Type",
+ "sensitive" : false
+ },
+ "Response Body Attribute Name" : {
+ "displayName" : "Response Body Attribute Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Name",
+ "sensitive" : false
+ },
+ "Request Digest Authentication Enabled" : {
+ "displayName" : "Request Digest Authentication Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Digest Authentication Enabled",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Name" : {
+ "displayName" : "Request Multipart Form-Data Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Name",
+ "sensitive" : false
+ },
+ "Response Cache Size" : {
+ "displayName" : "Response Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Size",
+ "sensitive" : false
+ },
+ "Response Body Ignored" : {
+ "displayName" : "Response Body Ignored",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Ignored",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.InvokeHTTP",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "My Generate FlowFile Processor",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "test" : "#{new param}",
+ "Batch Size" : "1",
+ "custom" : "custom",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "test" : {
+ "displayName" : "test",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "test",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "custom" : {
+ "displayName" : "custom",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "custom",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "* * * * * ?",
+ "schedulingStrategy" : "CRON_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "ERROR",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 2,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "10 sec",
+ "position" : {
+ "x" : -16.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "10 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : {
+ "Another one to delete" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ ],
+ "name" : "Another one to delete",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "secured",
+ "provided" : false,
+ "sensitive" : true
+ } ]
+ },
+ "Test Parameter Context" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ "Another one to delete" ],
+ "name" : "Test Parameter Context",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "new param",
+ "provided" : false,
+ "sensitive" : false,
+ "value" : "new value"
+ } ]
+ }
+ },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file
diff --git a/flow-diff/src/test/resources/flow_v5_property_parameter.json b/flow-diff/src/test/resources/flow_v5_property_parameter.json
new file mode 100644
index 0000000..ba49fdf
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v5_property_parameter.json
@@ -0,0 +1,651 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ {
+ "x" : 453.0,
+ "y" : 63.0
+ }, {
+ "x" : 453.0,
+ "y" : 113.0
+ } ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 MB",
+ "backPressureObjectThreshold" : 1000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "100 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT",
+ "loadBalanceStrategy" : "ROUND_ROBIN",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "name" : "InvokeHTTP",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "parameterContextName" : "Test Parameter Context",
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "InvokeHTTP",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -496.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Request Content-Encoding" : "DISABLED",
+ "nonSens" : "#{addedParam}",
+ "Request Multipart Form-Data Filename Enabled" : "true",
+ "Request Chunked Transfer-Encoding Enabled" : "false",
+ "HTTP/2 Disabled" : "False",
+ "Connection Timeout" : "5 secs",
+ "Response Cookie Strategy" : "DISABLED",
+ "Socket Read Timeout" : "15 secs",
+ "Socket Idle Connections" : "5",
+ "Request Body Enabled" : "true",
+ "sensitiveDynamicProp" : "#{newSensitiveParam}",
+ "HTTP URL" : "${url}",
+ "Socket Idle Timeout" : "5 mins",
+ "Response Redirects Enabled" : "True",
+ "Socket Write Timeout" : "15 secs",
+ "Response FlowFile Naming Strategy" : "RANDOM",
+ "Response Cache Enabled" : "false",
+ "Request Date Header Enabled" : "True",
+ "Request Failure Penalization Enabled" : "false",
+ "Response Body Attribute Size" : "256",
+ "Response Generation Required" : "false",
+ "Response Header Request Attributes Enabled" : "false",
+ "HTTP Method" : "GET",
+ "Request Content-Type" : "${mime.type}",
+ "Request Digest Authentication Enabled" : "false",
+ "Response Cache Size" : "10MB",
+ "Response Body Ignored" : "false"
+ },
+ "propertyDescriptors" : {
+ "Request Content-Encoding" : {
+ "displayName" : "Request Content-Encoding",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Encoding",
+ "sensitive" : false
+ },
+ "proxy-configuration-service" : {
+ "displayName" : "Proxy Configuration Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "proxy-configuration-service",
+ "sensitive" : false
+ },
+ "nonSens" : {
+ "displayName" : "nonSens",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "nonSens",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Filename Enabled" : {
+ "displayName" : "Request Multipart Form-Data Filename Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Filename Enabled",
+ "sensitive" : false
+ },
+ "Request Chunked Transfer-Encoding Enabled" : {
+ "displayName" : "Request Chunked Transfer-Encoding Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Chunked Transfer-Encoding Enabled",
+ "sensitive" : false
+ },
+ "HTTP/2 Disabled" : {
+ "displayName" : "HTTP/2 Disabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP/2 Disabled",
+ "sensitive" : false
+ },
+ "Connection Timeout" : {
+ "displayName" : "Connection Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Connection Timeout",
+ "sensitive" : false
+ },
+ "Response Cookie Strategy" : {
+ "displayName" : "Response Cookie Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cookie Strategy",
+ "sensitive" : false
+ },
+ "Request Password" : {
+ "displayName" : "Request Password",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Password",
+ "sensitive" : true
+ },
+ "Socket Read Timeout" : {
+ "displayName" : "Socket Read Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Read Timeout",
+ "sensitive" : false
+ },
+ "Socket Idle Connections" : {
+ "displayName" : "Socket Idle Connections",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Connections",
+ "sensitive" : false
+ },
+ "Request Body Enabled" : {
+ "displayName" : "Request Body Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Body Enabled",
+ "sensitive" : false
+ },
+ "sensitiveDynamicProp" : {
+ "displayName" : "sensitiveDynamicProp",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "sensitiveDynamicProp",
+ "sensitive" : true
+ },
+ "HTTP URL" : {
+ "displayName" : "HTTP URL",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP URL",
+ "sensitive" : false
+ },
+ "Request OAuth2 Access Token Provider" : {
+ "displayName" : "Request OAuth2 Access Token Provider",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "Request OAuth2 Access Token Provider",
+ "sensitive" : false
+ },
+ "Socket Idle Timeout" : {
+ "displayName" : "Socket Idle Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Timeout",
+ "sensitive" : false
+ },
+ "Response Redirects Enabled" : {
+ "displayName" : "Response Redirects Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Redirects Enabled",
+ "sensitive" : false
+ },
+ "Socket Write Timeout" : {
+ "displayName" : "Socket Write Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Write Timeout",
+ "sensitive" : false
+ },
+ "Request Header Attributes Pattern" : {
+ "displayName" : "Request Header Attributes Pattern",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Header Attributes Pattern",
+ "sensitive" : false
+ },
+ "Response FlowFile Naming Strategy" : {
+ "displayName" : "Response FlowFile Naming Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response FlowFile Naming Strategy",
+ "sensitive" : false
+ },
+ "Response Cache Enabled" : {
+ "displayName" : "Response Cache Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Enabled",
+ "sensitive" : false
+ },
+ "Request Date Header Enabled" : {
+ "displayName" : "Request Date Header Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Date Header Enabled",
+ "sensitive" : false
+ },
+ "Request Failure Penalization Enabled" : {
+ "displayName" : "Request Failure Penalization Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Failure Penalization Enabled",
+ "sensitive" : false
+ },
+ "Response Body Attribute Size" : {
+ "displayName" : "Response Body Attribute Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Size",
+ "sensitive" : false
+ },
+ "SSL Context Service" : {
+ "displayName" : "SSL Context Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "SSL Context Service",
+ "sensitive" : false
+ },
+ "Response Generation Required" : {
+ "displayName" : "Response Generation Required",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Generation Required",
+ "sensitive" : false
+ },
+ "Request User-Agent" : {
+ "displayName" : "Request User-Agent",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request User-Agent",
+ "sensitive" : false
+ },
+ "Response Header Request Attributes Enabled" : {
+ "displayName" : "Response Header Request Attributes Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Header Request Attributes Enabled",
+ "sensitive" : false
+ },
+ "HTTP Method" : {
+ "displayName" : "HTTP Method",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP Method",
+ "sensitive" : false
+ },
+ "Request Username" : {
+ "displayName" : "Request Username",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Username",
+ "sensitive" : false
+ },
+ "Request Content-Type" : {
+ "displayName" : "Request Content-Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Type",
+ "sensitive" : false
+ },
+ "Response Body Attribute Name" : {
+ "displayName" : "Response Body Attribute Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Name",
+ "sensitive" : false
+ },
+ "Request Digest Authentication Enabled" : {
+ "displayName" : "Request Digest Authentication Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Digest Authentication Enabled",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Name" : {
+ "displayName" : "Request Multipart Form-Data Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Name",
+ "sensitive" : false
+ },
+ "Response Cache Size" : {
+ "displayName" : "Response Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Size",
+ "sensitive" : false
+ },
+ "Response Body Ignored" : {
+ "displayName" : "Response Body Ignored",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Ignored",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "DISABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.InvokeHTTP",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "My Generate FlowFile Processor",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "Batch Size" : "1",
+ "custom" : "custom",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "custom" : {
+ "displayName" : "custom",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "custom",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "* * * * * ?",
+ "schedulingStrategy" : "CRON_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "ERROR",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 2,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "10 sec",
+ "position" : {
+ "x" : -16.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "10 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : {
+ "Another one to delete" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ ],
+ "name" : "Another one to delete",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "newSensitiveParam",
+ "provided" : false,
+ "sensitive" : true
+ }, {
+ "description" : "",
+ "name" : "secured",
+ "provided" : false,
+ "sensitive" : true
+ } ]
+ },
+ "Test Parameter Context" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ "Another one to delete" ],
+ "name" : "Test Parameter Context",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "addedParam",
+ "provided" : false,
+ "sensitive" : false,
+ "value" : "addedValue"
+ } ]
+ }
+ },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file
diff --git a/flow-diff/src/test/resources/flow_v6_parameter_value.json b/flow-diff/src/test/resources/flow_v6_parameter_value.json
new file mode 100644
index 0000000..83dced3
--- /dev/null
+++ b/flow-diff/src/test/resources/flow_v6_parameter_value.json
@@ -0,0 +1,642 @@
+{
+ "externalControllerServices" : { },
+ "flow" : {
+ "createdTimestamp" : 1726000168945,
+ "description" : "test",
+ "identifier" : "test",
+ "lastModifiedTimestamp" : 1726000168945,
+ "name" : "test",
+ "versionCount" : 0
+ },
+ "flowContents" : {
+ "comments" : "",
+ "componentType" : "PROCESS_GROUP",
+ "connections" : [ {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ {
+ "x" : 453.0,
+ "y" : 63.0
+ }, {
+ "x" : 453.0,
+ "y" : 113.0
+ } ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 MB",
+ "backPressureObjectThreshold" : 1000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "name" : "UpdateAttribute",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "100 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT",
+ "loadBalanceStrategy" : "ROUND_ROBIN",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ }, {
+ "backPressureDataSizeThreshold" : "1 GB",
+ "backPressureObjectThreshold" : 10000,
+ "bends" : [ ],
+ "componentType" : "CONNECTION",
+ "destination" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "name" : "InvokeHTTP",
+ "type" : "PROCESSOR"
+ },
+ "flowFileExpiration" : "0 sec",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829",
+ "labelIndex" : 0,
+ "loadBalanceCompression" : "DO_NOT_COMPRESS",
+ "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
+ "name" : "",
+ "partitioningAttribute" : "",
+ "prioritizers" : [ ],
+ "selectedRelationships" : [ "success" ],
+ "source" : {
+ "comments" : "",
+ "groupId" : "flow-contents-group",
+ "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "name" : "My Generate FlowFile Processor",
+ "type" : "PROCESSOR"
+ },
+ "zIndex" : 0
+ } ],
+ "controllerServices" : [ ],
+ "defaultBackPressureDataSizeThreshold" : "1 GB",
+ "defaultBackPressureObjectThreshold" : 10000,
+ "defaultFlowFileExpiration" : "0 sec",
+ "executionEngine" : "INHERITED",
+ "externalControllerServiceReferences" : { },
+ "flowFileConcurrency" : "UNBOUNDED",
+ "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
+ "funnels" : [ ],
+ "identifier" : "flow-contents-group",
+ "inputPorts" : [ ],
+ "labels" : [ ],
+ "maxConcurrentTasks" : 1,
+ "name" : "TestingFlowDiff",
+ "outputPorts" : [ ],
+ "parameterContextName" : "Test Parameter Context",
+ "position" : {
+ "x" : 0.0,
+ "y" : 0.0
+ },
+ "processGroups" : [ ],
+ "processors" : [ {
+ "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "InvokeHTTP",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -496.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Request Content-Encoding" : "DISABLED",
+ "nonSens" : "#{addedParam}",
+ "Request Multipart Form-Data Filename Enabled" : "true",
+ "Request Chunked Transfer-Encoding Enabled" : "false",
+ "HTTP/2 Disabled" : "False",
+ "Connection Timeout" : "5 secs",
+ "Response Cookie Strategy" : "DISABLED",
+ "Socket Read Timeout" : "15 secs",
+ "Socket Idle Connections" : "5",
+ "Request Body Enabled" : "true",
+ "HTTP URL" : "${url}",
+ "Socket Idle Timeout" : "5 mins",
+ "Response Redirects Enabled" : "True",
+ "Socket Write Timeout" : "15 secs",
+ "Response FlowFile Naming Strategy" : "RANDOM",
+ "Response Cache Enabled" : "false",
+ "Request Date Header Enabled" : "True",
+ "Request Failure Penalization Enabled" : "false",
+ "Response Body Attribute Size" : "256",
+ "Response Generation Required" : "false",
+ "Response Header Request Attributes Enabled" : "false",
+ "HTTP Method" : "GET",
+ "Request Content-Type" : "${mime.type}",
+ "Request Digest Authentication Enabled" : "false",
+ "Response Cache Size" : "10MB",
+ "Response Body Ignored" : "false"
+ },
+ "propertyDescriptors" : {
+ "Request Content-Encoding" : {
+ "displayName" : "Request Content-Encoding",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Encoding",
+ "sensitive" : false
+ },
+ "proxy-configuration-service" : {
+ "displayName" : "Proxy Configuration Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "proxy-configuration-service",
+ "sensitive" : false
+ },
+ "nonSens" : {
+ "displayName" : "nonSens",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "nonSens",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Filename Enabled" : {
+ "displayName" : "Request Multipart Form-Data Filename Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Filename Enabled",
+ "sensitive" : false
+ },
+ "Request Chunked Transfer-Encoding Enabled" : {
+ "displayName" : "Request Chunked Transfer-Encoding Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Chunked Transfer-Encoding Enabled",
+ "sensitive" : false
+ },
+ "HTTP/2 Disabled" : {
+ "displayName" : "HTTP/2 Disabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP/2 Disabled",
+ "sensitive" : false
+ },
+ "Connection Timeout" : {
+ "displayName" : "Connection Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Connection Timeout",
+ "sensitive" : false
+ },
+ "Response Cookie Strategy" : {
+ "displayName" : "Response Cookie Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cookie Strategy",
+ "sensitive" : false
+ },
+ "Request Password" : {
+ "displayName" : "Request Password",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Password",
+ "sensitive" : true
+ },
+ "Socket Read Timeout" : {
+ "displayName" : "Socket Read Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Read Timeout",
+ "sensitive" : false
+ },
+ "Socket Idle Connections" : {
+ "displayName" : "Socket Idle Connections",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Connections",
+ "sensitive" : false
+ },
+ "Request Body Enabled" : {
+ "displayName" : "Request Body Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Body Enabled",
+ "sensitive" : false
+ },
+ "sensitiveDynamicProp" : {
+ "displayName" : "sensitiveDynamicProp",
+ "dynamic" : true,
+ "identifiesControllerService" : false,
+ "name" : "sensitiveDynamicProp",
+ "sensitive" : true
+ },
+ "HTTP URL" : {
+ "displayName" : "HTTP URL",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP URL",
+ "sensitive" : false
+ },
+ "Request OAuth2 Access Token Provider" : {
+ "displayName" : "Request OAuth2 Access Token Provider",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "Request OAuth2 Access Token Provider",
+ "sensitive" : false
+ },
+ "Socket Idle Timeout" : {
+ "displayName" : "Socket Idle Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Idle Timeout",
+ "sensitive" : false
+ },
+ "Response Redirects Enabled" : {
+ "displayName" : "Response Redirects Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Redirects Enabled",
+ "sensitive" : false
+ },
+ "Socket Write Timeout" : {
+ "displayName" : "Socket Write Timeout",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Socket Write Timeout",
+ "sensitive" : false
+ },
+ "Request Header Attributes Pattern" : {
+ "displayName" : "Request Header Attributes Pattern",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Header Attributes Pattern",
+ "sensitive" : false
+ },
+ "Response FlowFile Naming Strategy" : {
+ "displayName" : "Response FlowFile Naming Strategy",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response FlowFile Naming Strategy",
+ "sensitive" : false
+ },
+ "Response Cache Enabled" : {
+ "displayName" : "Response Cache Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Enabled",
+ "sensitive" : false
+ },
+ "Request Date Header Enabled" : {
+ "displayName" : "Request Date Header Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Date Header Enabled",
+ "sensitive" : false
+ },
+ "Request Failure Penalization Enabled" : {
+ "displayName" : "Request Failure Penalization Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Failure Penalization Enabled",
+ "sensitive" : false
+ },
+ "Response Body Attribute Size" : {
+ "displayName" : "Response Body Attribute Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Size",
+ "sensitive" : false
+ },
+ "SSL Context Service" : {
+ "displayName" : "SSL Context Service",
+ "dynamic" : false,
+ "identifiesControllerService" : true,
+ "name" : "SSL Context Service",
+ "sensitive" : false
+ },
+ "Response Generation Required" : {
+ "displayName" : "Response Generation Required",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Generation Required",
+ "sensitive" : false
+ },
+ "Request User-Agent" : {
+ "displayName" : "Request User-Agent",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request User-Agent",
+ "sensitive" : false
+ },
+ "Response Header Request Attributes Enabled" : {
+ "displayName" : "Response Header Request Attributes Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Header Request Attributes Enabled",
+ "sensitive" : false
+ },
+ "HTTP Method" : {
+ "displayName" : "HTTP Method",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "HTTP Method",
+ "sensitive" : false
+ },
+ "Request Username" : {
+ "displayName" : "Request Username",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Username",
+ "sensitive" : false
+ },
+ "Request Content-Type" : {
+ "displayName" : "Request Content-Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Content-Type",
+ "sensitive" : false
+ },
+ "Response Body Attribute Name" : {
+ "displayName" : "Response Body Attribute Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Attribute Name",
+ "sensitive" : false
+ },
+ "Request Digest Authentication Enabled" : {
+ "displayName" : "Request Digest Authentication Enabled",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Digest Authentication Enabled",
+ "sensitive" : false
+ },
+ "Request Multipart Form-Data Name" : {
+ "displayName" : "Request Multipart Form-Data Name",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Request Multipart Form-Data Name",
+ "sensitive" : false
+ },
+ "Response Cache Size" : {
+ "displayName" : "Response Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Cache Size",
+ "sensitive" : false
+ },
+ "Response Body Ignored" : {
+ "displayName" : "Response Body Ignored",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Response Body Ignored",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "DISABLED",
+ "schedulingPeriod" : "0 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.InvokeHTTP",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "WARN",
+ "bundle" : {
+ "artifact" : "nifi-standard-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 1,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "My Generate FlowFile Processor",
+ "penaltyDuration" : "30 sec",
+ "position" : {
+ "x" : -286.0,
+ "y" : -250.0
+ },
+ "properties" : {
+ "character-set" : "UTF-8",
+ "File Size" : "0B",
+ "Batch Size" : "1",
+ "Unique FlowFiles" : "false",
+ "Data Format" : "Text"
+ },
+ "propertyDescriptors" : {
+ "character-set" : {
+ "displayName" : "Character Set",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "character-set",
+ "sensitive" : false
+ },
+ "File Size" : {
+ "displayName" : "File Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "File Size",
+ "sensitive" : false
+ },
+ "mime-type" : {
+ "displayName" : "Mime Type",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "mime-type",
+ "sensitive" : false
+ },
+ "generate-ff-custom-text" : {
+ "displayName" : "Custom Text",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "generate-ff-custom-text",
+ "sensitive" : false
+ },
+ "Batch Size" : {
+ "displayName" : "Batch Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Batch Size",
+ "sensitive" : false
+ },
+ "Unique FlowFiles" : {
+ "displayName" : "Unique FlowFiles",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Unique FlowFiles",
+ "sensitive" : false
+ },
+ "Data Format" : {
+ "displayName" : "Data Format",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Data Format",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 0,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "* * * * * ?",
+ "schedulingStrategy" : "CRON_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.standard.GenerateFlowFile",
+ "yieldDuration" : "1 sec"
+ }, {
+ "autoTerminatedRelationships" : [ ],
+ "backoffMechanism" : "PENALIZE_FLOWFILE",
+ "bulletinLevel" : "ERROR",
+ "bundle" : {
+ "artifact" : "nifi-update-attribute-nar",
+ "group" : "org.apache.nifi",
+ "version" : "2024.09.11-1"
+ },
+ "comments" : "",
+ "componentType" : "PROCESSOR",
+ "concurrentlySchedulableTaskCount" : 2,
+ "executionNode" : "ALL",
+ "groupIdentifier" : "flow-contents-group",
+ "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56",
+ "maxBackoffPeriod" : "10 mins",
+ "name" : "UpdateAttribute",
+ "penaltyDuration" : "10 sec",
+ "position" : {
+ "x" : -16.0,
+ "y" : 24.0
+ },
+ "properties" : {
+ "Store State" : "Do not store state",
+ "canonical-value-lookup-cache-size" : "100"
+ },
+ "propertyDescriptors" : {
+ "Delete Attributes Expression" : {
+ "displayName" : "Delete Attributes Expression",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Delete Attributes Expression",
+ "sensitive" : false
+ },
+ "Store State" : {
+ "displayName" : "Store State",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Store State",
+ "sensitive" : false
+ },
+ "canonical-value-lookup-cache-size" : {
+ "displayName" : "Cache Value Lookup Cache Size",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "canonical-value-lookup-cache-size",
+ "sensitive" : false
+ },
+ "Stateful Variables Initial Value" : {
+ "displayName" : "Stateful Variables Initial Value",
+ "dynamic" : false,
+ "identifiesControllerService" : false,
+ "name" : "Stateful Variables Initial Value",
+ "sensitive" : false
+ }
+ },
+ "retriedRelationships" : [ ],
+ "retryCount" : 10,
+ "runDurationMillis" : 25,
+ "scheduledState" : "ENABLED",
+ "schedulingPeriod" : "10 sec",
+ "schedulingStrategy" : "TIMER_DRIVEN",
+ "style" : { },
+ "type" : "org.apache.nifi.processors.attributes.UpdateAttribute",
+ "yieldDuration" : "1 sec"
+ } ],
+ "remoteProcessGroups" : [ ],
+ "scheduledState" : "ENABLED",
+ "statelessFlowTimeout" : "1 min"
+ },
+ "flowEncodingVersion" : "1.0",
+ "latest" : false,
+ "parameterContexts" : {
+ "Another one to delete" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ ],
+ "name" : "Another one to delete",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "newSensitiveParam",
+ "provided" : false,
+ "sensitive" : true
+ }, {
+ "description" : "",
+ "name" : "secured",
+ "provided" : false,
+ "sensitive" : true
+ } ]
+ },
+ "Test Parameter Context" : {
+ "componentType" : "PARAMETER_CONTEXT",
+ "inheritedParameterContexts" : [ "Another one to delete" ],
+ "name" : "Test Parameter Context",
+ "parameters" : [ {
+ "description" : "",
+ "name" : "addedParam",
+ "provided" : false,
+ "sensitive" : false,
+ "value" : "newValue"
+ } ]
+ }
+ },
+ "parameterProviders" : { },
+ "snapshotMetadata" : {
+ "author" : "pvillard@datavolo.io",
+ "flowIdentifier" : "test",
+ "timestamp" : 0
+ }
+}
\ No newline at end of file