You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:40 UTC
[41/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring
project structure to better isolate extensions
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/user-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/user-guide.adoc b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/user-guide.adoc
new file mode 100644
index 0000000..e746e12
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/user-guide.adoc
@@ -0,0 +1,365 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+= Apache NiFi Registry User Guide
+Apache NiFi Team <de...@nifi.apache.org>
+:homepage: http://nifi.apache.org
+
+
+== Introduction
+Apache NiFi Registry—a subproject of Apache NiFi—is a complementary application that provides a central location for storage and management of shared resources across one or more instances of NiFi and/or MiNiFi.
+
+The first implementation of the Registry supports versioned flows. Process group level dataflows created in NiFi can be placed under version control and stored in a registry. The registry organizes where flows are stored and manages the permissions to access, create, modify or delete them.
+
+See the link:administration-guide.html[System Administrator’s Guide] for information about Registry system requirements, installation, and configuration. Once NiFi Registry is installed, use a supported web browser to view the UI.
+
+
+== Browser Support
+[options="header"]
+|======================
+|Browser |Version
+|Chrome |Current and Current - 1
+|FireFox |Current and Current - 1
+|Safari |Current and Current - 1
+|======================
+
+Current and Current - 1 indicates that the UI is supported in the current stable release of that browser and the preceding one. For instance, if the current stable release is 62.X then the officially supported versions will be 62.X and 61.X.
+
+For Safari, which releases major versions much less frequently, Current and Current - 1 simply represent the two latest releases.
+
+The supported browser versions are driven by the capabilities the UI employs and the dependencies it uses. UI features will be developed and tested against the supported browsers. Any problem using a supported browser should be reported to Apache NiFi.
+
+=== Unsupported Browsers
+
+While the UI may run successfully in unsupported browsers, it is not actively tested against them. Additionally, the UI is designed as a desktop experience and is not currently supported in mobile browsers.
+
+=== Viewing the UI in Variably Sized Browsers
+In most environments, all of the UI is visible in your browser. However, the UI has a responsive design that allows you to scroll through screens as needed, in smaller sized browsers or tablet environments.
+
+NOTE: The minimum recommended screen size is 1080px X 445px.
+
+== Terminology
+
+*Flow*: A process group level NiFi dataflow that has been placed under version control and saved to the Registry.
+
+*Bucket*: A container that stores and organizes flows.
+
+*Policy*: Defines a user or group's ability to import, view, commit changes and/or delete flows.
+
+
+[[User_Interface]]
+== NiFi Registry User Interface
+
+The NiFi Registry UI displays the shared resources available and provides mechanisms for creating and administering users/groups, buckets and policies.
+
+When the application is started, the user is able to navigate to the UI by going to the default address of `http://<hostname>:18080/nifi-registry` in a web browser. There are no permissions configured by default, so anyone is able to view and modify the flows and buckets. For information on securing the system, see the link:administration-guide.html[System Administrator’s Guide].
+
+When an administrator navigates to the UI for the first time, the registry is empty as there are no flow resources available to share yet:
+
+image::nifi-registry-components.png["NiFi Registry Components"]
+
+The Buckets menu is available at the top left of the screen. It allows the user to display flows based on which bucket they are contained in. On the top right of the screen is the Settings button (image:iconSettings.png["Settings Icon"]) which accesses functionality for managing users, groups, buckets and policies. Next to the Settings button is the Help button (image:iconHelp.png["Help Icon"]) which accesses the NiFi Registry Documentation.
+
+[[logging-in]]
+== Logging In
+
+If NiFi Registry is configured to run securely, users will have to be granted permissions to buckets by an administrator. For information on configuring NiFi Registry to run securely, see the link:administration-guide.html[System Administrator’s Guide].
+
+If the user is logging in with their username/password they will be presented with a screen to do so.
+
+image::loginRegistry.png["NiFi Registry Login"]
+
+
+== Manage Flows
+
+=== View a Flow
+Flows in all buckets are listed in the main window of the UI by default. If the registry is secured, only the flows in the buckets that the user has access to are listed.
+
+image::flows_all.png["All Flows"]
+
+To see the flows in a particular bucket, select that bucket from the drop-down menu at the top left of the UI.
+
+image::bucket_menu.png["Bucket Menu"]
+
+Click on a flow to see its Description and Change Log:
+
+image::flow_change_log.png["Flow Change Log"]
+
+The Change Log includes all versions that were saved for a flow. Clicking on the version reveals details about when the version was saved, which user committed the save, and any comments entered by the user.
+
+==== Sorting & Filtering Flows
+Flows can be sorted alphabetically by Name (ascending or descending) or by Update (newest or oldest) using the drop-down at the top right of the UI.
+
+image::flows_sort_menu.png["Flows Sort Menu"]
+
+The flow list can be filtered by:
+
+* flow name
+* flow description
+* flow ID
+* bucket name
+* bucket ID
+
+Here is an example filtering by flow name:
+
+image::flows_filter_by_name.png["Flows Filter By Name"]
+
+=== Delete a Flow
+To delete a flow from the registry:
+
+1. Click on the flow to see its details.
+2. Select the "Actions" drop-down and click the "Delete" menu option.
++
+image::flow_delete_action.png["Flow Delete Action"]
+3. Select "Delete" to confirm.
++
+image::flow_delete_confirm.png["Flow Delete Confirm"]
+
+WARNING: It is possible to delete a flow that is actively being used in NiFi.
+
+
+== Manage Buckets
+
+To manage buckets, enter the Administration section of the Registry by clicking the Settings button (image:iconSettings.png["Settings Icon"]) on the top right of the UI. The Buckets window appears by default.
+
+=== Sorting & Filtering Buckets
+Buckets can be sorted alphabetically by Name (ascending or descending) using the up/down arrows.
+
+image::buckets_sort_by_name.png["Buckets Sort By Name"]
+
+The buckets listed can be filtered by:
+
+* bucket name
+* bucket description
+* bucket ID
+
+Here is an example filtering by bucket name:
+
+image::buckets_filter_by_name.png["Buckets Filter By Name"]
+
+=== Create a Bucket
+1. Select the "New Bucket" button.
++
+image::new_bucket_button.png["New Bucket Button"]
+2. Enter the desired bucket name and select the "Create" button.
++
+image::new_bucket_dialog.png["New Bucket Dialog"]
+
+NOTE: To quickly create multiple buckets, check "Keep this dialog open after creating bucket".
+
+
+=== Delete a Bucket
+1. Select the Delete button (image:iconDelete.png["Delete Icon"]) in the row of the bucket.
++
+image::delete_bucket_single.png["Delete Single Bucket"]
+2. From the Delete Bucket dialog, select "Delete".
++
+image::delete_bucket_dialog.png["Delete Bucket Dialog"]
+
+=== Delete Multiple Buckets
+1. Select the checkboxes in the rows of the desired buckets to delete.
++
+image::check_multiple_buckets.png["Check Multiple Buckets"]
+2. Select the "Actions" drop-down and click the "Delete" option.
++
+image::delete_multiple_buckets.png["Delete Multiple Buckets"]
+3. From the Delete Buckets dialog, select "Delete".
++
+image::delete_buckets_dialog.png["Delete Buckets Dialog"]
+
+=== Edit a Bucket Name
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the bucket.
++
+image::manage_bucket.png["Manage Bucket"]
+2. Enter a new name for the bucket and select the "Save" button.
++
+image::bucket_nav_name_edit.png["Edit Bucket Name"]
+
+=== Bucket Policies
+Bucket policies define user privileges on buckets/flows in the Registry and in NiFi. The available permissions are:
+
+* *All* - In the Registry, the assigned user is able to view and delete flows in the bucket. In NiFi, the selected user is able to import flows from the bucket and commit changes to flows in the bucket.
+
+* *Read* - In the Registry, the assigned user is able to view flows in the bucket. In NiFi, the selected user is able to import flows from the bucket.
+
+* *Write* - In NiFi, the assigned user is able to commit changes to flows in the bucket.
+
+* *Delete* - In the Registry, the assigned user is able to delete flows in the bucket.
+
+NOTE: Users would typically have Read permissions at a minimum. A user with Write permission would not commit changes to a flow if they were not able to import it initially. A user with Delete permission would not delete a flow if they could not view it.
+
+NOTE: If a user has a bucket policy and the group that the user is in also has a policy, all policies are used to determine access. For example, assume User1 is in Group1, User1 has READ privileges on Bucket1 and Group1 has READ privileges on Bucket2. In this scenario, User1 will have READ privileges on both Bucket1 and Bucket2.
+
+==== Create a Bucket Policy
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the bucket.
+2. Select the "New Policy" button.
++
+image::new_bucket_policy_create.png["Create New Bucket Policy"]
+3. Select a user, check the desired permissions and select the "Apply" button:
++
+image::new_bucket_policy_user_permission.png["New Bucket Policy User and Permissions"]
+4. The policy is added to the bucket:
++
+image::new_bucket_policy_added.png["New Bucket Policy Added"]
+
+==== Delete a Bucket Policy
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the bucket.
+2. Select the Delete button (image:iconDelete.png["Delete Icon"]) in the row of the policy.
++
+image::delete_bucket_policy.png["Delete Policy"]
+3. From the Delete Policy dialog, select "Delete".
++
+image::delete_bucket_policy_dialog.png["Delete Policy Dialog"]
+
+
+== Manage Users & Groups
+
+To manage users/groups, enter the Administration section of the Registry by clicking the Settings button (image:iconSettings.png["Settings Icon"]) on the top right of the UI. Select Users from the top menu to open the Users window.
+
+=== Sorting & Filtering Users/Groups
+Users/groups can be sorted alphabetically by Name (ascending or descending) using the up/down arrows.
+
+image::users_sort_by_name.png["Users Sort By Name"]
+
+The Users/groups listed can be filtered by:
+
+* user name
+* user ID
+* group name
+* group ID
+
+Here is an example of filtering by user name:
+
+image::users_filter_by_name.png["Users Filter By Name"]
+
+=== Add a User
+1. Select the "Add User" button.
++
+image::add_user_button.png["Add User"]
+2. Enter the desired username or appropriate Identity information. Select the "Add" button.
++
+image::add_user_dialog.png["New User Dialog"]
+
+NOTE: To quickly create multiple users, check "Keep this dialog open after adding user".
+
+=== Delete a User
+1. Select the Delete button (image:iconDelete.png["Delete Icon"]) in the row of the user.
++
+image::delete_user_single.png["Delete Single User"]
+2. From the Delete User dialog, select "Delete".
++
+image::delete_user_dialog.png["Delete User Dialog"]
+
+=== Delete Multiple Users
+1. Select the checkboxes in the rows of the desired users to delete.
++
+image::check_multiple_users.png["Check Multiple Users"]
+2. Select the "Actions" drop-down and click the "Delete" option.
++
+image::delete_multiple_users.png["Delete Multiple Users"]
+3. From the Delete Users dialog, select "Delete".
++
+image::delete_users_groups_dialog.png["Delete Users Dialog"]
+
+
+=== Edit a User Name
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the user.
++
+image::manage_user.png["Manage User"]
+2. Enter a new user name and select the "Save" button.
++
+image::user_nav_name_edit.png["Edit User Name"]
+
+WARNING: Some users cannot have their names edited. For example, those defined by LDAP. These users will be specially highlighted in the list.
+
+image::users_non_configurable.png["Non-configurable Users"]
+
+=== Special Privileges
+Special privileges are additional permissions that allow a user to manage or access certain aspects of the Registry. The special privileges are:
+
+* *Can manage buckets* - Allow a user to manage all buckets in the registry, as well as provide the user access to all buckets from a connected system (e.g., NiFi).
+
+* *Can manage users* - Allow a user to manage all registry users and groups.
+
+* *Can manage policies* - Allow a user to grant all registry users read, write, and delete permission to a bucket.
+
+* *Can proxy user requests* - Allow a connected system (e.g., NiFi) to process requests of authorized users of that system.
+
+==== Grant Special Privileges to a User
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the user.
++
+image::manage_user.png["Manage User"]
+2. Check the desired privileges:
++
+image::user_special_privileges.png["User Special Privileges"]
+3. Changes made to special privileges are automatically saved.
+
+== Manage Groups
+
+=== Add an Empty Group
+1. With no users checked, select the "Actions" drop-down and click the "Create new group" option.
++
+image::create_new_group.png["Create New Group"]
+2. Enter a name for the Group and select the "Create" button.
++
+image::create_new_group_dialog.png["Create New Group Dialog"]
+
+NOTE: To quickly create multiple empty groups, check "Keep this dialog open after creating group".
+
+
+=== Add User to a Group
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the user.
+2. Select the "Add To Group" button.
++
+image::user_nav_add_to_group.png["Add User to Group"]
+3. In the "Add User to Groups" dialog, select the group(s) to add the user to. Select the "Add" button when all desired groups have been selected.
++
+image::add_user_to_groups_dialog.png["Add User to Groups Dialog"]
+4. The user is added to the group:
++
+image::group_added.png["Group Added"]
+
+NOTE: Groups cannot contain other groups.
+
+=== Create a New Group with Selected Users
+1. Select the checkboxes in the rows of the desired users. From the "Actions" drop-down, click the "Create new group" option.
++
+image::select_users_create_new_group.png["Select Users for New Group"]
+2. Enter a name for the Group and select the "Create" button.
++
+image::select_users_create_new_group_dialog.png["Create New Group Dialog"]
+3. The new group is created with the selected users as members:
++
+image::select_users_new_group_added.png["New Group Added with Selected Users"]
+
+=== Remove a User from a Group
+There are two ways to remove a user from a group.
+
+==== User Window
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the user.
+2. In the Membership section of the window, select the Remove button (image:iconDelete.png["Delete Icon"]) in the row of the group.
++
+image::remove_group_from_user.png["Remove Group From User"]
+
+==== Group Window
+1. Select the Manage button (image:iconManage.png["Manage Icon"]) in the row of the group. The Members tab is selected by default.
+2. In the Membership section of the window, select the Remove button (image:iconDelete.png["Delete Icon"]) in the row of the user.
++
+image::remove_user_from_group.png["Remove User From Group"]
+
+=== Other Group Level Actions
+
+Editing group names, deleting groups, adding policies to/deleting policies from groups and granting special privileges to groups follow similar procedures described earlier for corresponding user level actions.
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docs/src/main/assembly/dependencies.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docs/src/main/assembly/dependencies.xml b/nifi-registry-core/nifi-registry-docs/src/main/assembly/dependencies.xml
new file mode 100644
index 0000000..6f6279b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docs/src/main/assembly/dependencies.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+ <id>resources</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <fileSets>
+ <fileSet>
+ <directory>${project.build.directory}/generated-docs/</directory>
+ <outputDirectory>/html/</outputDirectory>
+ </fileSet>
+ </fileSets>
+ <files>
+ <file>
+ <source>./LICENSE</source>
+ <outputDirectory>./</outputDirectory>
+ <destName>LICENSE</destName>
+ <fileMode>0644</fileMode>
+ <filtered>true</filtered>
+ </file>
+ <file>
+ <source>./NOTICE</source>
+ <outputDirectory>./</outputDirectory>
+ <destName>NOTICE</destName>
+ <fileMode>0644</fileMode>
+ <filtered>true</filtered>
+ </file>
+ </files>
+</assembly>
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/pom.xml b/nifi-registry-core/nifi-registry-flow-diff/pom.xml
new file mode 100644
index 0000000..d6af7c6
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ You under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-core</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-registry-flow-diff</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-data-model</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ComparableDataFlow.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ComparableDataFlow.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ComparableDataFlow.java
new file mode 100644
index 0000000..603b7cf
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ComparableDataFlow.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+
+public interface ComparableDataFlow {
+ String getName();
+
+ VersionedProcessGroup getContents();
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ConciseEvolvingDifferenceDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ConciseEvolvingDifferenceDescriptor.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ConciseEvolvingDifferenceDescriptor.java
new file mode 100644
index 0000000..76fdfc5
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/ConciseEvolvingDifferenceDescriptor.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Objects;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+import org.apache.nifi.registry.flow.VersionedFlowCoordinates;
+
+/**
+ * Describes differences between flows as if Flow A is an 'earlier version' of the same flow than Flow B.
+ * This provides verbiage such as "Processor with ID 123 was added to flow."
+ */
+public class ConciseEvolvingDifferenceDescriptor implements DifferenceDescriptor {
+
+ @Override
+ public String describeDifference(final DifferenceType type, final String flowAName, final String flowBName, final VersionedComponent componentA,
+ final VersionedComponent componentB, final String fieldName, final Object valueA, final Object valueB) {
+
+ final String description;
+ switch (type) {
+ case COMPONENT_ADDED:
+ description = String.format("%s was added", componentB.getComponentType().getTypeName());
+ break;
+ case COMPONENT_REMOVED:
+ description = String.format("%s was removed", componentA.getComponentType().getTypeName());
+ break;
+ case PROPERTY_ADDED:
+ description = String.format("Property '%s' was added", fieldName);
+ break;
+ case PROPERTY_REMOVED:
+ description = String.format("Property '%s' was removed", fieldName);
+ break;
+ case VARIABLE_ADDED:
+ description = String.format("Variable '%s' was added", fieldName);
+ break;
+ case VARIABLE_REMOVED:
+ description = String.format("Variable '%s' was removed", fieldName);
+ break;
+ case POSITION_CHANGED:
+ description = "Position was changed";
+ break;
+ case BENDPOINTS_CHANGED:
+ description = "Connection Bendpoints changed";
+ break;
+ case VERSIONED_FLOW_COORDINATES_CHANGED:
+ if (valueA instanceof VersionedFlowCoordinates && valueB instanceof VersionedFlowCoordinates) {
+ final VersionedFlowCoordinates coordinatesA = (VersionedFlowCoordinates) valueA;
+ final VersionedFlowCoordinates coordinatesB = (VersionedFlowCoordinates) valueB;
+
+ // If the two vary only by version, then use a more concise message. If anything else is different, then use a fully explanation.
+ if (Objects.equals(coordinatesA.getRegistryUrl(), coordinatesB.getRegistryUrl()) && Objects.equals(coordinatesA.getBucketId(), coordinatesB.getBucketId())
+ && Objects.equals(coordinatesA.getFlowId(), coordinatesB.getFlowId()) && coordinatesA.getVersion() != coordinatesB.getVersion()) {
+
+ description = String.format("Flow Version changed from %s to %s", coordinatesA.getVersion(), coordinatesB.getVersion());
+ break;
+ }
+ }
+
+ description = String.format("From '%s' to '%s'", valueA, valueB);
+ break;
+ default:
+ description = String.format("From '%s' to '%s'", valueA, valueB);
+ break;
+ }
+
+ return description;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceDescriptor.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceDescriptor.java
new file mode 100644
index 0000000..56e65ef
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceDescriptor.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+
+public interface DifferenceDescriptor {
+ /**
+ * Describes a difference between two flows
+ *
+ * @param type the difference
+ * @param componentA the component in "Flow A"
+ * @param componentB the component in "Flow B"
+ * @param fieldName the name of the field that changed, or <code>null</code> if the field name does not apply for the difference type
+ * @param valueA the value being compared from "Flow A"
+ * @param valueB the value being compared from "Flow B"
+ * @return a human-readable description of how the flows differ
+ */
+ String describeDifference(DifferenceType type, String flowAName, String flowBName, VersionedComponent componentA, VersionedComponent componentB, String fieldName,
+ Object valueA, Object valueB);
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceType.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceType.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceType.java
new file mode 100644
index 0000000..047f557
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/DifferenceType.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+public enum DifferenceType {
+ /**
+ * The component does not exist in Flow A but exists in Flow B
+ */
+ COMPONENT_ADDED("Component Added"),
+
+ /**
+ * The component exists in Flow A but does not exist in Flow B
+ */
+ COMPONENT_REMOVED("Component Removed"),
+
+ /**
+ * The component has a different name in each of the flows
+ */
+ NAME_CHANGED("Component Name Changed"),
+
+ /**
+ * The component has a different type in each of the flows
+ */
+ TYPE_CHANGED("Component Type Changed"),
+
+ /**
+ * The component has a different bundle in each of the flows
+ */
+ BUNDLE_CHANGED("Component Bundle Changed"),
+
+ /**
+ * The component has a different penalty duration in each of the flows
+ */
+ PENALTY_DURATION_CHANGED("Penalty Duration Changed"),
+
+ /**
+ * The component has a different yield duration in each of the flows
+ */
+ YIELD_DURATION_CHANGED("Yield Duration Changed"),
+
+ /**
+ * The component has a different bulletin level in each of the flows
+ */
+ BULLETIN_LEVEL_CHANGED("Bulletin Level Changed"),
+
+ /**
+ * The component has a different set of Auto-Terminated Relationships in each of the flows
+ */
+ AUTO_TERMINATED_RELATIONSHIPS_CHANGED("Auto-Terminated Relationships Changed"),
+
+ /**
+ * The component has a different scheduling strategy in each of the flows
+ */
+ SCHEDULING_STRATEGY_CHANGED("Scheduling Strategy Changed"),
+
+ /**
+ * The component has a different maximum number of concurrent tasks in each of the flows
+ */
+ CONCURRENT_TASKS_CHANGED("Concurrent Tasks Changed"),
+
+ /**
+ * The component has a different run schedule in each of the flows
+ */
+ RUN_SCHEDULE_CHANGED("Run Schedule Changed"),
+
+ /**
+ * The component has a different execution mode in each of the flows
+ */
+ EXECUTION_MODE_CHANGED("Execution Mode Changed"),
+
+ /**
+ * The component has a different run duration in each of the flows
+ */
+ RUN_DURATION_CHANGED("Run Duration Changed"),
+
+ /**
+ * The component has a different value in each of the flows for a specific property
+ */
+ PROPERTY_CHANGED("Property Value Changed"),
+
+ /**
+ * Property does not exist in Flow A but does exist in Flow B
+ */
+ PROPERTY_ADDED("Property Added"),
+
+ /**
+ * Property exists in Flow A but does not exist in Flow B
+ */
+ PROPERTY_REMOVED("Property Removed"),
+
+ /**
+ * The component has a different value for the Annotation Data in each of the flows
+ */
+ ANNOTATION_DATA_CHANGED("Annotation Data (Advanced UI Configuration) Changed"),
+
+ /**
+ * The component has a different comment in each of the flows
+ */
+ COMMENTS_CHANGED("Comments Changed"),
+
+ /**
+ * The position of the component on the graph is different in each of the flows
+ */
+ POSITION_CHANGED("Position Changed"),
+
+ /**
+ * The stylistic configuration of the component is different in each of the flows
+ */
+ STYLE_CHANGED("Style Changed"),
+
+ /**
+ * The Relationships included in a connection is different in each of the flows
+ */
+ SELECTED_RELATIONSHIPS_CHANGED("Selected Relationships Changed"),
+
+ /**
+ * The Connection has a different set of Prioritizers in each of the flows
+ */
+ PRIORITIZERS_CHANGED("Prioritizers Changed"),
+
+ /**
+ * The Connection has a different value for the FlowFile Expiration in each of the flows
+ */
+ FLOWFILE_EXPIRATION_CHANGED("FlowFile Expiration Changed"),
+
+ /**
+ * The Connection has a different value for the Object Backpressure Threshold in each of the flows
+ */
+ BACKPRESSURE_OBJECT_THRESHOLD_CHANGED("Backpressure Object Threshold Changed"),
+
+ /**
+ * The Connection has a different value for the Data Size Backpressure Threshold in each of the flows
+ */
+ BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED("Backpressure Data Size Threshold Changed"),
+
+ /**
+ * The Connection has a different value for the Load Balance Strategy in each of the flows
+ */
+ LOAD_BALANCE_STRATEGY_CHANGED("Load-Balance Strategy Changed"),
+
+ /**
+ * The Connection has a different value for the Partitioning Attribute in each of the flows
+ */
+ PARTITIONING_ATTRIBUTE_CHANGED("Partitioning Attribute Changed"),
+
+ /**
+ * The Connection has a different value for the Load Balancing Compression in each of the flows
+ */
+ LOAD_BALANCE_COMPRESSION_CHANGED("Load-Balance Compression Changed"),
+
+ /**
+ * The Connection has a different set of Bend Points in each of the flows
+ */
+ BENDPOINTS_CHANGED("Connection Bend Points Changed"),
+
+ /**
+ * The Connection has a difference Source in each of the flows
+ */
+ SOURCE_CHANGED("Connection Source Changed"),
+
+ /**
+ * The Connection has a difference Destination in each of the flows
+ */
+ DESTINATION_CHANGED("Connection Destination Changed"),
+
+ /**
+ * The value in the Label is different in each of the flows
+ */
+ LABEL_VALUE_CHANGED("Label Text Changed"),
+
+ /**
+ * The variable does not exist in Flow A but exists in Flow B
+ */
+ VARIABLE_ADDED("Variable Added to Process Group"),
+
+ /**
+ * The variable does not exist in Flow B but exists in Flow A
+ */
+ VARIABLE_REMOVED("Variable Removed from Process Group"),
+
+ /**
+ * The API of the Controller Service is different in each of the flows
+ */
+ SERVICE_API_CHANGED("Controller Service API Changed"),
+
+ /**
+ * The Remote Process Group has a different Transport Protocol in each of the flows
+ */
+ RPG_TRANSPORT_PROTOCOL_CHANGED("Remote Process Group Transport Protocol Changed"),
+
+ /**
+ * The Remote Process Group has a different Proxy Host in each of the flows
+ */
+ RPG_PROXY_HOST_CHANGED("Remote Process Group Proxy Host Changed"),
+
+ /**
+ * The Remote Process Group has a different Proxy Port in each of the flows
+ */
+ RPG_PROXY_PORT_CHANGED("Remote Process Group Proxy Port Changed"),
+
+ /**
+ * The Remote Process Group has a different Proxy User in each of the flows
+ */
+ RPG_PROXY_USER_CHANGED("Remote Process Group Proxy User Changed"),
+
+ /**
+ * The Remote Process Group has a different Network Interface chosen in each of the flows
+ */
+ RPG_NETWORK_INTERFACE_CHANGED("Remote Process Group Network Interface Changed"),
+
+ /**
+ * The Remote Process Group has a different Communications Timeout in each of the flows
+ */
+ RPG_COMMS_TIMEOUT_CHANGED("Remote Process Group Communications Timeout Changed"),
+
+ /**
+ * The Remote Input Port or Remote Output Port has a different Batch Size in each of the flows
+ */
+ REMOTE_PORT_BATCH_SIZE_CHANGED("Remote Process Group Port's Batch Size Changed"),
+
+ /**
+ * The Remote Input Port or Remote Output Port has a different value for the Compression flag in each of the flows
+ */
+ REMOTE_PORT_COMPRESSION_CHANGED("Remote Process Group Port's Compression Flag Changed"),
+
+ /**
+ * The Process Group points to a different Versioned Flow in each of the flows
+ */
+ VERSIONED_FLOW_COORDINATES_CHANGED("Versioned Flow Coordinates Changed");
+
+ private final String description;
+
+ private DifferenceType(final String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/EvolvingDifferenceDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/EvolvingDifferenceDescriptor.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/EvolvingDifferenceDescriptor.java
new file mode 100644
index 0000000..c3300e6
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/EvolvingDifferenceDescriptor.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+
+/**
+ * Describes differences between flows as if Flow A is an 'earlier version' of the same flow than Flow B.
+ * This provides verbiage such as "Processor with ID 123 was added to flow."
+ */
+public class EvolvingDifferenceDescriptor implements DifferenceDescriptor {
+
+ @Override
+ public String describeDifference(final DifferenceType type, final String flowAName, final String flowBName, final VersionedComponent componentA,
+ final VersionedComponent componentB, final String fieldName, final Object valueA, final Object valueB) {
+
+ final String description;
+ switch (type) {
+ case COMPONENT_ADDED:
+ description = String.format("%s with ID %s was added to flow", componentB.getComponentType().getTypeName(), componentB.getIdentifier());
+ break;
+ case COMPONENT_REMOVED:
+ description = String.format("%s with ID %s was removed from flow", componentA.getComponentType().getTypeName(), componentA.getIdentifier());
+ break;
+ case PROPERTY_ADDED:
+ description = String.format("Property '%s' was added to %s with ID %s", fieldName, componentB.getComponentType().getTypeName(), componentB.getIdentifier());
+ break;
+ case PROPERTY_REMOVED:
+ description = String.format("Property '%s' was removed from %s with ID %s", fieldName, componentA.getComponentType().getTypeName(), componentA.getIdentifier());
+ break;
+ case VARIABLE_ADDED:
+ description = String.format("Variable '%s' was added to Process Group with ID %s", fieldName, componentB.getIdentifier());
+ break;
+ case VARIABLE_REMOVED:
+ description = String.format("Variable '%s' was removed from Process Group with ID %s", fieldName, componentA.getIdentifier());
+ break;
+ default:
+ description = String.format("%s for %s with ID %s from '%s' to '%s'",
+ type.getDescription(), componentA.getComponentType().getTypeName(), componentA.getIdentifier(), valueA, valueB);
+ break;
+ }
+
+ return description;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparator.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparator.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparator.java
new file mode 100644
index 0000000..3835fec
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparator.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Set;
+
+import org.apache.nifi.registry.flow.VersionedControllerService;
+
+public interface FlowComparator {
+ FlowComparison compare();
+
+ /**
+ * Compares to versions of a Controller Service and returns the differences between them
+ *
+ * @param serviceA the first Controller Service
+ * @param serviceB the second Controller Service
+ * @return the differences between them
+ */
+ Set<FlowDifference> compareControllerServices(VersionedControllerService serviceA, VersionedControllerService serviceB);
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparison.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparison.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparison.java
new file mode 100644
index 0000000..52b3f26
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowComparison.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Set;
+
+public interface FlowComparison {
+ ComparableDataFlow getFlowA();
+
+ ComparableDataFlow getFlowB();
+
+ Set<FlowDifference> getDifferences();
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowDifference.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowDifference.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowDifference.java
new file mode 100644
index 0000000..249bf42
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/FlowDifference.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Optional;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+
+public interface FlowDifference {
+ DifferenceType getDifferenceType();
+
+ VersionedComponent getComponentA();
+
+ VersionedComponent getComponentB();
+
+ Optional<String> getFieldName();
+
+ Object getValueA();
+
+ Object getValueB();
+
+ String getDescription();
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardComparableDataFlow.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardComparableDataFlow.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardComparableDataFlow.java
new file mode 100644
index 0000000..649dbc3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardComparableDataFlow.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+
+public class StandardComparableDataFlow implements ComparableDataFlow {
+ private final String name;
+ private final VersionedProcessGroup contents;
+
+ public StandardComparableDataFlow(final String name, final VersionedProcessGroup contents) {
+ this.name = name;
+ this.contents = contents;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public VersionedProcessGroup getContents() {
+ return contents;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
new file mode 100644
index 0000000..f98225e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+import org.apache.nifi.registry.flow.VersionedConnection;
+import org.apache.nifi.registry.flow.VersionedControllerService;
+import org.apache.nifi.registry.flow.VersionedFunnel;
+import org.apache.nifi.registry.flow.VersionedLabel;
+import org.apache.nifi.registry.flow.VersionedPort;
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.flow.VersionedProcessor;
+import org.apache.nifi.registry.flow.VersionedPropertyDescriptor;
+import org.apache.nifi.registry.flow.VersionedRemoteGroupPort;
+import org.apache.nifi.registry.flow.VersionedRemoteProcessGroup;
+
+public class StandardFlowComparator implements FlowComparator {
+ private static final String DEFAULT_LOAD_BALANCE_STRATEGY = "DO_NOT_LOAD_BALANCE";
+ private static final String DEFAULT_PARTITIONING_ATTRIBUTE = "";
+ private static final String DEFAULT_LOAD_BALANCE_COMPRESSION = "DO_NOT_COMPRESS";
+
+ private final ComparableDataFlow flowA;
+ private final ComparableDataFlow flowB;
+ private final Set<String> externallyAccessibleServiceIds;
+ private final DifferenceDescriptor differenceDescriptor;
+
+ public StandardFlowComparator(final ComparableDataFlow flowA, final ComparableDataFlow flowB,
+ final Set<String> externallyAccessibleServiceIds, final DifferenceDescriptor differenceDescriptor) {
+ this.flowA = flowA;
+ this.flowB = flowB;
+ this.externallyAccessibleServiceIds = externallyAccessibleServiceIds;
+ this.differenceDescriptor = differenceDescriptor;
+ }
+
+ @Override
+ public FlowComparison compare() {
+ final VersionedProcessGroup groupA = flowA.getContents();
+ final VersionedProcessGroup groupB = flowB.getContents();
+ final Set<FlowDifference> differences = compare(groupA, groupB);
+
+ return new StandardFlowComparison(flowA, flowB, differences);
+ }
+
+ private Set<FlowDifference> compare(final VersionedProcessGroup groupA, final VersionedProcessGroup groupB) {
+ final Set<FlowDifference> differences = new HashSet<>();
+ // Note that we do not compare the names, because when we import a Flow into NiFi, we may well give it a new name.
+ // Child Process Groups' names will still compare but the main group that is under Version Control will not
+ compare(groupA, groupB, differences, false);
+ return differences;
+ }
+
+
+ private <T extends VersionedComponent> Set<FlowDifference> compareComponents(final Set<T> componentsA, final Set<T> componentsB, final ComponentComparator<T> comparator) {
+ final Map<String, T> componentMapA = byId(componentsA == null ? Collections.emptySet() : componentsA);
+ final Map<String, T> componentMapB = byId(componentsB == null ? Collections.emptySet() : componentsB);
+
+ final Set<FlowDifference> differences = new HashSet<>();
+
+ componentMapA.forEach((key, componentA) -> {
+ final T componentB = componentMapB.get(key);
+ comparator.compare(componentA, componentB, differences);
+ });
+
+ componentMapB.forEach((key, componentB) -> {
+ final T componentA = componentMapA.get(key);
+
+ // if component A is not null, it has already been compared above. If component A
+ // is null, then it is missing from Flow A but present in Flow B, so we will just call
+ // compare(), which will handle this for us.
+ if (componentA == null) {
+ comparator.compare(componentA, componentB, differences);
+ }
+ });
+
+ return differences;
+ }
+
+
+ private boolean compareComponents(final VersionedComponent componentA, final VersionedComponent componentB, final Set<FlowDifference> differences) {
+ return compareComponents(componentA, componentB, differences, true, true, true);
+ }
+
+ private boolean compareComponents(final VersionedComponent componentA, final VersionedComponent componentB, final Set<FlowDifference> differences,
+ final boolean compareName, final boolean comparePos, final boolean compareComments) {
+ if (componentA == null) {
+ differences.add(difference(DifferenceType.COMPONENT_ADDED, componentA, componentB, componentA, componentB));
+ return true;
+ }
+
+ if (componentB == null) {
+ differences.add(difference(DifferenceType.COMPONENT_REMOVED, componentA, componentB, componentA, componentB));
+ return true;
+ }
+
+ if (compareComments) {
+ addIfDifferent(differences, DifferenceType.COMMENTS_CHANGED, componentA, componentB, VersionedComponent::getComments, false);
+ }
+
+ if (compareName) {
+ addIfDifferent(differences, DifferenceType.NAME_CHANGED, componentA, componentB, VersionedComponent::getName);
+ }
+
+ if (comparePos) {
+ addIfDifferent(differences, DifferenceType.POSITION_CHANGED, componentA, componentB, VersionedComponent::getPosition);
+ }
+
+ return false;
+ }
+
+ private void compare(final VersionedProcessor processorA, final VersionedProcessor processorB, final Set<FlowDifference> differences) {
+ if (compareComponents(processorA, processorB, differences)) {
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.ANNOTATION_DATA_CHANGED, processorA, processorB, VersionedProcessor::getAnnotationData);
+ addIfDifferent(differences, DifferenceType.AUTO_TERMINATED_RELATIONSHIPS_CHANGED, processorA, processorB, VersionedProcessor::getAutoTerminatedRelationships);
+ addIfDifferent(differences, DifferenceType.BULLETIN_LEVEL_CHANGED, processorA, processorB, VersionedProcessor::getBulletinLevel);
+ addIfDifferent(differences, DifferenceType.BUNDLE_CHANGED, processorA, processorB, VersionedProcessor::getBundle);
+ addIfDifferent(differences, DifferenceType.CONCURRENT_TASKS_CHANGED, processorA, processorB, VersionedProcessor::getConcurrentlySchedulableTaskCount);
+ addIfDifferent(differences, DifferenceType.EXECUTION_MODE_CHANGED, processorA, processorB, VersionedProcessor::getExecutionNode);
+ addIfDifferent(differences, DifferenceType.PENALTY_DURATION_CHANGED, processorA, processorB, VersionedProcessor::getPenaltyDuration);
+ addIfDifferent(differences, DifferenceType.RUN_DURATION_CHANGED, processorA, processorB, VersionedProcessor::getRunDurationMillis);
+ addIfDifferent(differences, DifferenceType.RUN_SCHEDULE_CHANGED, processorA, processorB, VersionedProcessor::getSchedulingPeriod);
+ addIfDifferent(differences, DifferenceType.SCHEDULING_STRATEGY_CHANGED, processorA, processorB, VersionedProcessor::getSchedulingStrategy);
+ addIfDifferent(differences, DifferenceType.STYLE_CHANGED, processorA, processorB, VersionedProcessor::getStyle);
+ addIfDifferent(differences, DifferenceType.YIELD_DURATION_CHANGED, processorA, processorB, VersionedProcessor::getYieldDuration);
+ compareProperties(processorA, processorB, processorA.getProperties(), processorB.getProperties(), processorA.getPropertyDescriptors(), processorB.getPropertyDescriptors(), differences);
+ }
+
+ @Override
+ public Set<FlowDifference> compareControllerServices(final VersionedControllerService serviceA, final VersionedControllerService serviceB) {
+ final Set<FlowDifference> differences = new HashSet<>();
+ compare(serviceA, serviceB, differences);
+ return differences;
+ }
+
+ private void compare(final VersionedControllerService serviceA, final VersionedControllerService serviceB, final Set<FlowDifference> differences) {
+ if (compareComponents(serviceA, serviceB, differences)) {
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.ANNOTATION_DATA_CHANGED, serviceA, serviceB, VersionedControllerService::getAnnotationData);
+ addIfDifferent(differences, DifferenceType.BUNDLE_CHANGED, serviceA, serviceB, VersionedControllerService::getBundle);
+ compareProperties(serviceA, serviceB, serviceA.getProperties(), serviceB.getProperties(), serviceA.getPropertyDescriptors(), serviceB.getPropertyDescriptors(), differences);
+ }
+
+
+ private void compareProperties(final VersionedComponent componentA, final VersionedComponent componentB,
+ final Map<String, String> propertiesA, final Map<String, String> propertiesB,
+ final Map<String, VersionedPropertyDescriptor> descriptorsA, final Map<String, VersionedPropertyDescriptor> descriptorsB,
+ final Set<FlowDifference> differences) {
+
+ propertiesA.forEach((key, valueA) -> {
+ final String valueB = propertiesB.get(key);
+
+ VersionedPropertyDescriptor descriptor = descriptorsA.get(key);
+ if (descriptor == null) {
+ descriptor = descriptorsB.get(key);
+ }
+
+ final String displayName;
+ if (descriptor == null) {
+ displayName = key;
+ } else {
+ displayName = descriptor.getDisplayName() == null ? descriptor.getName() : descriptor.getDisplayName();
+ }
+
+ if (valueA == null && valueB != null) {
+ differences.add(difference(DifferenceType.PROPERTY_ADDED, componentA, componentB, displayName, valueA, valueB));
+ } else if (valueA != null && valueB == null) {
+ differences.add(difference(DifferenceType.PROPERTY_REMOVED, componentA, componentB, displayName, valueA, valueB));
+ } else if (valueA != null && !valueA.equals(valueB)) {
+ // If the property in Flow A references a Controller Service that is not available in the flow
+ // and the property in Flow B references a Controller Service that is available in its environment
+ // but not part of the Versioned Flow, then we do not want to consider this to be a Flow Difference.
+ // This is typically the case when a flow is versioned in one instance, referencing an external Controller Service,
+ // and then imported into another NiFi instance. When imported, the property does not point to any existing Controller
+ // Service, and the user must then point the property an existing Controller Service. We don't want to consider the
+ // flow as having changed, since it is an environment-specific change (similar to how we handle variables).
+ if (descriptor != null && descriptor.getIdentifiesControllerService()) {
+ final boolean accessibleA = externallyAccessibleServiceIds.contains(valueA);
+ final boolean accessibleB = externallyAccessibleServiceIds.contains(valueB);
+ if (!accessibleA && accessibleB) {
+ return;
+ }
+ }
+
+ differences.add(difference(DifferenceType.PROPERTY_CHANGED, componentA, componentB, displayName, valueA, valueB));
+ }
+ });
+
+ propertiesB.forEach((key, valueB) -> {
+ final String valueA = propertiesA.get(key);
+
+ // If there are any properties for component B that do not exist for Component A, add those as differences as well.
+ if (valueA == null && valueB != null) {
+ final VersionedPropertyDescriptor descriptor = descriptorsB.get(key);
+
+ final String displayName;
+ if (descriptor == null) {
+ displayName = key;
+ } else {
+ displayName = descriptor.getDisplayName() == null ? descriptor.getName() : descriptor.getDisplayName();
+ }
+
+ differences.add(difference(DifferenceType.PROPERTY_ADDED, componentA, componentB, displayName, null, valueB));
+ }
+ });
+ }
+
+
+ private void compare(final VersionedFunnel funnelA, final VersionedFunnel funnelB, final Set<FlowDifference> differences) {
+ compareComponents(funnelA, funnelB, differences);
+ }
+
+ private void compare(final VersionedLabel labelA, final VersionedLabel labelB, final Set<FlowDifference> differences) {
+ if (compareComponents(labelA, labelB, differences)) {
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.LABEL_VALUE_CHANGED, labelA, labelB, VersionedLabel::getLabel);
+ addIfDifferent(differences, DifferenceType.POSITION_CHANGED, labelA, labelB, VersionedLabel::getHeight);
+ addIfDifferent(differences, DifferenceType.POSITION_CHANGED, labelA, labelB, VersionedLabel::getWidth);
+ addIfDifferent(differences, DifferenceType.STYLE_CHANGED, labelA, labelB, VersionedLabel::getStyle);
+ }
+
+ private void compare(final VersionedPort portA, final VersionedPort portB, final Set<FlowDifference> differences) {
+ compareComponents(portA, portB, differences);
+ }
+
+ private void compare(final VersionedRemoteProcessGroup rpgA, final VersionedRemoteProcessGroup rpgB, final Set<FlowDifference> differences) {
+ if (compareComponents(rpgA, rpgB, differences, false, true, false)) { // do not compare comments for RPG because they come from remote system, not our local flow
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.RPG_COMMS_TIMEOUT_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getCommunicationsTimeout);
+ addIfDifferent(differences, DifferenceType.RPG_NETWORK_INTERFACE_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getLocalNetworkInterface);
+ addIfDifferent(differences, DifferenceType.RPG_PROXY_HOST_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getProxyHost);
+ addIfDifferent(differences, DifferenceType.RPG_PROXY_PORT_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getProxyPort);
+ addIfDifferent(differences, DifferenceType.RPG_PROXY_USER_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getProxyUser);
+ addIfDifferent(differences, DifferenceType.RPG_TRANSPORT_PROTOCOL_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getTransportProtocol);
+ addIfDifferent(differences, DifferenceType.YIELD_DURATION_CHANGED, rpgA, rpgB, VersionedRemoteProcessGroup::getYieldDuration);
+
+ differences.addAll(compareComponents(rpgA.getInputPorts(), rpgB.getInputPorts(), this::compare));
+ differences.addAll(compareComponents(rpgA.getOutputPorts(), rpgB.getOutputPorts(), this::compare));
+ }
+
+ private void compare(final VersionedRemoteGroupPort portA, final VersionedRemoteGroupPort portB, final Set<FlowDifference> differences) {
+ if (compareComponents(portA, portB, differences)) {
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.REMOTE_PORT_BATCH_SIZE_CHANGED, portA, portB, VersionedRemoteGroupPort::getBatchSize);
+ addIfDifferent(differences, DifferenceType.REMOTE_PORT_COMPRESSION_CHANGED, portA, portB, VersionedRemoteGroupPort::isUseCompression);
+ addIfDifferent(differences, DifferenceType.CONCURRENT_TASKS_CHANGED, portA, portB, VersionedRemoteGroupPort::getConcurrentlySchedulableTaskCount);
+ }
+
+
+ private void compare(final VersionedProcessGroup groupA, final VersionedProcessGroup groupB, final Set<FlowDifference> differences, final boolean compareNamePos) {
+ if (compareComponents(groupA, groupB, differences, compareNamePos, compareNamePos, true)) {
+ return;
+ }
+
+ if (groupA == null) {
+ differences.add(difference(DifferenceType.COMPONENT_ADDED, groupA, groupB, groupA, groupB));
+ return;
+ }
+
+ if (groupB == null) {
+ differences.add(difference(DifferenceType.COMPONENT_REMOVED, groupA, groupB, groupA, groupB));
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.VERSIONED_FLOW_COORDINATES_CHANGED, groupA, groupB, VersionedProcessGroup::getVersionedFlowCoordinates);
+
+ if (groupA.getVersionedFlowCoordinates() == null && groupB.getVersionedFlowCoordinates() == null) {
+ differences.addAll(compareComponents(groupA.getConnections(), groupB.getConnections(), this::compare));
+ differences.addAll(compareComponents(groupA.getProcessors(), groupB.getProcessors(), this::compare));
+ differences.addAll(compareComponents(groupA.getControllerServices(), groupB.getControllerServices(), this::compare));
+ differences.addAll(compareComponents(groupA.getFunnels(), groupB.getFunnels(), this::compare));
+ differences.addAll(compareComponents(groupA.getInputPorts(), groupB.getInputPorts(), this::compare));
+ differences.addAll(compareComponents(groupA.getLabels(), groupB.getLabels(), this::compare));
+ differences.addAll(compareComponents(groupA.getOutputPorts(), groupB.getOutputPorts(), this::compare));
+ differences.addAll(compareComponents(groupA.getProcessGroups(), groupB.getProcessGroups(), (a, b, diffs) -> compare(a, b, diffs, true)));
+ differences.addAll(compareComponents(groupA.getRemoteProcessGroups(), groupB.getRemoteProcessGroups(), this::compare));
+ }
+ }
+
+
+ private void compare(final VersionedConnection connectionA, final VersionedConnection connectionB, final Set<FlowDifference> differences) {
+ if (compareComponents(connectionA, connectionB, differences)) {
+ return;
+ }
+
+ addIfDifferent(differences, DifferenceType.BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED, connectionA, connectionB, VersionedConnection::getBackPressureDataSizeThreshold);
+ addIfDifferent(differences, DifferenceType.BACKPRESSURE_OBJECT_THRESHOLD_CHANGED, connectionA, connectionB, VersionedConnection::getBackPressureObjectThreshold);
+ addIfDifferent(differences, DifferenceType.BENDPOINTS_CHANGED, connectionA, connectionB, VersionedConnection::getBends);
+ addIfDifferent(differences, DifferenceType.DESTINATION_CHANGED, connectionA, connectionB, VersionedConnection::getDestination);
+ addIfDifferent(differences, DifferenceType.FLOWFILE_EXPIRATION_CHANGED, connectionA, connectionB, VersionedConnection::getFlowFileExpiration);
+ addIfDifferent(differences, DifferenceType.PRIORITIZERS_CHANGED, connectionA, connectionB, VersionedConnection::getPrioritizers);
+ addIfDifferent(differences, DifferenceType.SELECTED_RELATIONSHIPS_CHANGED, connectionA, connectionB, VersionedConnection::getSelectedRelationships);
+ addIfDifferent(differences, DifferenceType.SOURCE_CHANGED, connectionA, connectionB, c -> c.getSource().getId());
+
+ addIfDifferent(differences, DifferenceType.LOAD_BALANCE_STRATEGY_CHANGED, connectionA, connectionB,
+ conn -> conn.getLoadBalanceStrategy() == null ? DEFAULT_LOAD_BALANCE_STRATEGY : conn.getLoadBalanceStrategy());
+
+ addIfDifferent(differences, DifferenceType.PARTITIONING_ATTRIBUTE_CHANGED, connectionA, connectionB,
+ conn -> conn.getPartitioningAttribute() == null ? DEFAULT_PARTITIONING_ATTRIBUTE : conn.getPartitioningAttribute());
+
+ addIfDifferent(differences, DifferenceType.LOAD_BALANCE_COMPRESSION_CHANGED, connectionA, connectionB,
+ conn -> conn.getLoadBalanceCompression() == null ? DEFAULT_LOAD_BALANCE_COMPRESSION : conn.getLoadBalanceCompression());
+ }
+
+
+ private <T extends VersionedComponent> Map<String, T> byId(final Set<T> components) {
+ return components.stream().collect(Collectors.toMap(VersionedComponent::getIdentifier, Function.identity()));
+ }
+
+ private <T extends VersionedComponent> void addIfDifferent(final Set<FlowDifference> differences, final DifferenceType type, final T componentA, final T componentB,
+ final Function<T, Object> transform) {
+
+ addIfDifferent(differences, type, componentA, componentB, transform, true);
+ }
+
+ private <T extends VersionedComponent> void addIfDifferent(final Set<FlowDifference> differences, final DifferenceType type, final T componentA, final T componentB,
+ final Function<T, Object> transform, final boolean differentiateNullAndEmptyString) {
+
+ final Object valueA = transform.apply(componentA);
+ final Object valueB = transform.apply(componentB);
+
+ if (Objects.equals(valueA, valueB)) {
+ return;
+ }
+
+ // We don't want to disambiguate between an empty collection and null.
+ if ((valueA == null || valueA instanceof Collection) && (valueB == null || valueB instanceof Collection) && isEmpty((Collection<?>) valueA) && isEmpty((Collection<?>) valueB)) {
+ return;
+ }
+
+ if (!differentiateNullAndEmptyString && isEmptyString(valueA) && isEmptyString(valueB)) {
+ return;
+ }
+
+ differences.add(difference(type, componentA, componentB, null, valueA, valueB));
+ }
+
+ private boolean isEmpty(final Collection<?> collection) {
+ return collection == null || collection.isEmpty();
+ }
+
+ private boolean isEmptyString(final Object potentialString) {
+ if (potentialString == null) {
+ return true;
+ }
+
+ if (potentialString instanceof String) {
+ final String string = (String) potentialString;
+ return string.isEmpty();
+ } else {
+ return false;
+ }
+ }
+
+ private FlowDifference difference(final DifferenceType type, final VersionedComponent componentA, final VersionedComponent componentB,
+ final Object valueA, final Object valueB) {
+ return difference(type, componentA, componentB, null, valueA, valueB);
+ }
+
+ private FlowDifference difference(final DifferenceType type, final VersionedComponent componentA, final VersionedComponent componentB, final String fieldName,
+ final Object valueA, final Object valueB) {
+
+ final String description = differenceDescriptor.describeDifference(type, flowA.getName(), flowB.getName(), componentA, componentB, fieldName, valueA, valueB);
+ return new StandardFlowDifference(type, componentA, componentB, valueA, valueB, description);
+ }
+
+
+ private static interface ComponentComparator<T extends VersionedComponent> {
+ void compare(T componentA, T componentB, Set<FlowDifference> differences);
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparison.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparison.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparison.java
new file mode 100644
index 0000000..3245994
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparison.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class StandardFlowComparison implements FlowComparison {
+
+ private final ComparableDataFlow flowA;
+ private final ComparableDataFlow flowB;
+ private final Set<FlowDifference> differences;
+
+ public StandardFlowComparison(final ComparableDataFlow flowA, final ComparableDataFlow flowB) {
+ this.flowA = flowA;
+ this.flowB = flowB;
+ this.differences = new HashSet<>();
+ }
+
+ public StandardFlowComparison(final ComparableDataFlow flowA, final ComparableDataFlow flowB, final Set<FlowDifference> differences) {
+ this.flowA = flowA;
+ this.flowB = flowB;
+ this.differences = differences;
+ }
+
+ @Override
+ public ComparableDataFlow getFlowA() {
+ return flowA;
+ }
+
+ @Override
+ public ComparableDataFlow getFlowB() {
+ return flowB;
+ }
+
+ @Override
+ public Set<FlowDifference> getDifferences() {
+ return Collections.unmodifiableSet(differences);
+ }
+
+ public void addDifference(final FlowDifference difference) {
+ this.differences.add(difference);
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowDifference.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowDifference.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowDifference.java
new file mode 100644
index 0000000..054ee5f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowDifference.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+
+public class StandardFlowDifference implements FlowDifference {
+ private final DifferenceType type;
+ private final VersionedComponent componentA;
+ private final VersionedComponent componentB;
+ private final Optional<String> fieldName;
+ private final Object valueA;
+ private final Object valueB;
+ private final String description;
+
+ public StandardFlowDifference(final DifferenceType type, final VersionedComponent componentA, final VersionedComponent componentB, final Object valueA, final Object valueB,
+ final String description) {
+ this(type, componentA, componentB, null, valueA, valueB, description);
+ }
+
+ public StandardFlowDifference(final DifferenceType type, final VersionedComponent componentA, final VersionedComponent componentB, final String fieldName,
+ final Object valueA, final Object valueB, final String description) {
+ this.type = type;
+ this.componentA = componentA;
+ this.componentB = componentB;
+ this.fieldName = Optional.ofNullable(fieldName);
+ this.valueA = valueA;
+ this.valueB = valueB;
+ this.description = description;
+ }
+
+ @Override
+ public DifferenceType getDifferenceType() {
+ return type;
+ }
+
+ @Override
+ public VersionedComponent getComponentA() {
+ return componentA;
+ }
+
+ @Override
+ public VersionedComponent getComponentB() {
+ return componentB;
+ }
+
+ @Override
+ public Optional<String> getFieldName() {
+ return fieldName;
+ }
+
+ @Override
+ public Object getValueA() {
+ return valueA;
+ }
+
+ @Override
+ public Object getValueB() {
+ return valueB;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 + 17 * (componentA == null ? 0 : componentA.getIdentifier().hashCode()) +
+ 17 * (componentB == null ? 0 : componentB.getIdentifier().hashCode()) +
+ Objects.hash(description, type, valueA, valueB);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof StandardFlowDifference)) {
+ return false;
+ }
+ final StandardFlowDifference other = (StandardFlowDifference) obj;
+ final String componentAId = componentA == null ? null : componentA.getIdentifier();
+ final String otherComponentAId = other.componentA == null ? null : other.componentA.getIdentifier();
+
+ final String componentBId = componentB == null ? null : componentB.getIdentifier();
+ final String otherComponentBId = other.componentB == null ? null : other.componentB.getIdentifier();
+
+ return Objects.equals(componentAId, otherComponentAId) && Objects.equals(componentBId, otherComponentBId)
+ && Objects.equals(description, other.description) && Objects.equals(type, other.type)
+ && Objects.equals(valueA, other.valueA) && Objects.equals(valueB, other.valueB);
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StaticDifferenceDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StaticDifferenceDescriptor.java b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StaticDifferenceDescriptor.java
new file mode 100644
index 0000000..5bf82c1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StaticDifferenceDescriptor.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.registry.flow.diff;
+
+import java.util.Objects;
+
+import org.apache.nifi.registry.flow.VersionedComponent;
+import org.apache.nifi.registry.flow.VersionedFlowCoordinates;
+
+/**
+ * Describes differences between flows as if the flows are two disparate flows that are being
+ * compared to one another. This provides verbiage such as "Processor with ID 123 exists in Flow A but not in Flow B."
+ */
+public class StaticDifferenceDescriptor implements DifferenceDescriptor {
+
+ @Override
+ public String describeDifference(final DifferenceType type, final String flowAName, final String flowBName, final VersionedComponent componentA,
+ final VersionedComponent componentB, final String fieldName, final Object valueA, final Object valueB) {
+
+ final String description;
+ switch (type) {
+ case COMPONENT_ADDED:
+ description = String.format("%s with ID %s exists in %s but not in %s",
+ componentB.getComponentType().getTypeName(), componentB.getIdentifier(), flowBName, flowAName);
+ break;
+ case COMPONENT_REMOVED:
+ description = String.format("%s with ID %s exists in %s but not in %s",
+ componentA.getComponentType().getTypeName(), componentA.getIdentifier(), flowAName, flowBName);
+ break;
+ case PROPERTY_ADDED:
+ description = String.format("Property '%s' exists for %s with ID %s in %s but not in %s",
+ fieldName, componentB.getComponentType().getTypeName(), componentB.getIdentifier(), flowBName, flowAName);
+ break;
+ case PROPERTY_REMOVED:
+ description = String.format("Property '%s' exists for %s with ID %s in %s but not in %s",
+ fieldName, componentA.getComponentType().getTypeName(), componentA.getIdentifier(), flowAName, flowBName);
+ break;
+ case VARIABLE_ADDED:
+ description = String.format("Variable '%s' exists for Process Group with ID %s in %s but not in %s",
+ fieldName, componentB.getIdentifier(), flowBName, flowAName);
+ break;
+ case VARIABLE_REMOVED:
+ description = String.format("Variable '%s' exists for Process Group with ID %s in %s but not in %s",
+ fieldName, componentA.getIdentifier(), flowAName, flowBName);
+ break;
+ case VERSIONED_FLOW_COORDINATES_CHANGED:
+ if (valueA instanceof VersionedFlowCoordinates && valueB instanceof VersionedFlowCoordinates) {
+ final VersionedFlowCoordinates coordinatesA = (VersionedFlowCoordinates) valueA;
+ final VersionedFlowCoordinates coordinatesB = (VersionedFlowCoordinates) valueB;
+
+ // If the two vary only by version, then use a more concise message. If anything else is different, then use a fully explanation.
+ if (Objects.equals(coordinatesA.getRegistryUrl(), coordinatesB.getRegistryUrl()) && Objects.equals(coordinatesA.getBucketId(), coordinatesB.getBucketId())
+ && Objects.equals(coordinatesA.getFlowId(), coordinatesB.getFlowId()) && coordinatesA.getVersion() != coordinatesB.getVersion()) {
+
+ description = String.format("Flow Version is %s in %s but %s in %s", coordinatesA.getVersion(), flowAName, coordinatesB.getVersion(), flowBName);
+ break;
+ }
+ }
+
+ description = String.format("%s for %s with ID %s; flow '%s' has value %s; flow '%s' has value %s",
+ type.getDescription(), componentA.getComponentType().getTypeName(), componentA.getIdentifier(),
+ flowAName, valueA, flowBName, valueB);
+ break;
+ default:
+ description = String.format("%s for %s with ID %s; flow '%s' has value %s; flow '%s' has value %s",
+ type.getDescription(), componentA.getComponentType().getTypeName(), componentA.getIdentifier(),
+ flowAName, valueA, flowBName, valueB);
+ break;
+ }
+
+ return description;
+ }
+
+}