summaryrefslogtreecommitdiffstats
path: root/mod/designtool
diff options
context:
space:
mode:
authorAndrew Gauld <agauld@att.com>2020-02-07 15:00:39 +0000
committerAndrew Gauld <agauld@att.com>2020-02-07 20:37:14 +0000
commit9507f2f8d2ec616f01f5ee8825106300b95e8ddc (patch)
tree846762f63ff485c7d8615c64d751231ce603de81 /mod/designtool
parent83f85998b01c7937b84549d81f5ec2a07958f96d (diff)
Add DCAE MOD design tool project
Change-Id: I660b28ebfaa7e4b5f03a1df5fd17d126f58b7c14 Issue-ID: DCAEGEN2-1860 Signed-off-by: Andrew Gauld <agauld@att.com>
Diffstat (limited to 'mod/designtool')
-rw-r--r--mod/designtool/README.md37
-rw-r--r--mod/designtool/designtool-web/Dockerfile77
-rw-r--r--mod/designtool/designtool-web/pom.xml255
-rwxr-xr-xmod/designtool/designtool-web/sh/applypatches.sh107
-rwxr-xr-xmod/designtool/designtool-web/sh/common.sh43
-rwxr-xr-xmod/designtool/designtool-web/sh/start.sh97
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/NiFi.java446
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/controller/AbstractPort.java675
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEAutoLoader.java105
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEClassLoaders.java127
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/util/NiFiProperties.java1551
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java4899
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java4354
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java182
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java700
-rw-r--r--mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/server/JettyServer.java1226
-rw-r--r--mod/designtool/designtool-web/src/main/resources/filters/canvas-min.properties19
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/pages/canvas.jsp167
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp191
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp218
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/distribution-environment-dialog.jsp42
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp33
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp129
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp86
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/shell.jsp34
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/css/navigation.css338
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/images/dcae-logo.pngbin0 -> 36755 bytes
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/jquery/dcae-mod.js135
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js288
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js1150
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js1587
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js1990
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js1744
-rw-r--r--mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js2373
-rwxr-xr-xmod/designtool/nifi-war-to-jar/extract.sh33
-rw-r--r--mod/designtool/nifi-war-to-jar/pom.xml62
-rw-r--r--mod/designtool/pom.xml58
37 files changed, 25558 insertions, 0 deletions
diff --git a/mod/designtool/README.md b/mod/designtool/README.md
new file mode 100644
index 0000000..11a9bda
--- /dev/null
+++ b/mod/designtool/README.md
@@ -0,0 +1,37 @@
+# DCAE MOD's Design tool
+
+## License
+
+Copyright 2020 AT&T Intellectual Property. All rights reserved.
+
+This file is licensed under the CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL LICENSE
+
+Full license text at https://creativecommons.org/licenses/by/4.0/legalcode
+
+
+## Description
+
+DCAE MOD's DCAE design tool is based on Nifi 1.9.2 with modifications
+made by the DCAE MOD team.
+
+## Development
+
+The designtool-web module contains the modified versions of Nifi files, along
+with a Dockerfile and a script (sh/applypatches.sh) for replacing them in
+the nifi Docker image, to produce the design tool Docker image.
+
+If the set of modified files changes, then the Dockerfile, the script, and
+potentially the pom.xml may require changes.
+
+In particular, note that the Nifi build creates 2 "bin" files, one for nifi
+itself and the other for the nifi-toolkit, which are expanded into separate
+directories in the nifi image. Contained in the "bin" files are "nar" files,
+which contain "jar" and "war" files. And, inside the nifi-web-ui "war" file
+are several "-all.js" and "-all.css" files, containing minified aggregations
+of the various js and css source files. The applypatches script needs to
+appropriately patch these nar, war, jar, all.js, and all-css files (some of
+which also have gzipped versions).
+
+The nifi-war-to-jar module builds a jar archive from the classes in the
+nifi-web-api war archive, that the modified files in the designtool-web
+module can be compiled against.
diff --git a/mod/designtool/designtool-web/Dockerfile b/mod/designtool/designtool-web/Dockerfile
new file mode 100644
index 0000000..f4559ff
--- /dev/null
+++ b/mod/designtool/designtool-web/Dockerfile
@@ -0,0 +1,77 @@
+# ============LICENSE_START=====================================================
+# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+# ==============================================================================
+# 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.
+# ============LICENSE_END=======================================================
+
+FROM apache/nifi:${nifi.version} as original
+
+FROM openjdk:8 as artifactbase
+
+ENV NIFI_BASE_DIR /opt/nifi
+
+COPY --from=original $NIFI_BASE_DIR $NIFI_BASE_DIR
+
+ADD sh/ /tmp/patches
+
+COPY target/designtool-web-${project.version}.war /tmp/patches
+
+RUN bash /tmp/patches/applypatches.sh ${project.version} ${nifi.version}
+
+FROM openjdk:8-jre
+
+ARG UID=1000
+ARG GID=1000
+
+ENV NIFI_BASE_DIR /opt/nifi
+ENV NIFI_HOME ${NIFI_BASE_DIR}/nifi-current
+ENV NIFI_TOOLKIT_HOME ${NIFI_BASE_DIR}/nifi-toolkit-current
+ENV NIFI_PID_DIR=${NIFI_HOME}/run
+ENV NIFI_LOG_DIR=${NIFI_HOME}/logs
+
+# Setup NiFi user and create necessary directories
+RUN groupadd -g ${GID} nifi || groupmod -n nifi `getent group ${GID} | cut -d: -f1` \
+ && useradd --shell /bin/bash -u ${UID} -g ${GID} -m nifi \
+ && apt-get update \
+ && apt-get install -y jq xmlstarlet procps
+
+COPY --chown=nifi:nifi --from=artifactbase $NIFI_BASE_DIR $NIFI_BASE_DIR
+
+VOLUME ${NIFI_LOG_DIR} \
+ ${NIFI_HOME}/conf \
+ ${NIFI_HOME}/database_repository \
+ ${NIFI_HOME}/flowfile_repository \
+ ${NIFI_HOME}/content_repository \
+ ${NIFI_HOME}/provenance_repository \
+ ${NIFI_HOME}/state
+
+USER nifi
+
+# Clear nifi-env.sh in favour of configuring all environment variables in the Dockerfile
+RUN echo "#!/bin/sh\n" > $NIFI_HOME/bin/nifi-env.sh
+
+# Web HTTP(s) & Socket Site-to-Site Ports
+EXPOSE 8080 8443 10000
+
+WORKDIR ${NIFI_HOME}
+
+# Apply configuration and start NiFi
+#
+# We need to use the exec form to avoid running our command in a subshell and omitting signals,
+# thus being unable to shut down gracefully:
+# https://docs.docker.com/engine/reference/builder/#entrypoint
+#
+# Also we need to use relative path, because the exec form does not invoke a command shell,
+# thus normal shell processing does not happen:
+# https://docs.docker.com/engine/reference/builder/#exec-form-entrypoint-example
+ENTRYPOINT ["../scripts/start.sh"]
diff --git a/mod/designtool/designtool-web/pom.xml b/mod/designtool/designtool-web/pom.xml
new file mode 100644
index 0000000..7cf0d8a
--- /dev/null
+++ b/mod/designtool/designtool-web/pom.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<!--
+================================================================================
+Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+================================================================================
+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.
+============LICENSE_END=========================================================
+
+-->
+<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.onap.dcaegen2.platform.mod</groupId>
+ <artifactId>designtool</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>designtool-web</artifactId>
+ <packaging>war</packaging>
+ <name>dcaegen2-platform-mod-designtool-web</name>
+ <properties>
+ <canvas.filter>canvas-min.properties</canvas.filter>
+ </properties>
+ <repositories>
+ <repository>
+ <id>jcenter</id>
+ <url>https://jcenter.bintray.com</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ </repository>
+ </repositories>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-cluster</artifactId>
+ <version>${nifi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-ui-extension</artifactId>
+ <version>${nifi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-jetty</artifactId>
+ <version>${nifi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-documentation</artifactId>
+ <version>${nifi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-web-content-access</artifactId>
+ <version>${nifi.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ <version>${org.slf4j.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.dcaegen2.platform.mod</groupId>
+ <artifactId>nifi-war-to-jar</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-annotations</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-deploy</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <filters>
+ <filter>src/main/resources/filters/${canvas.filter}</filter>
+ </filters>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-jspc-maven-plugin</artifactId>
+ <version>${jetty.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jspc</goal>
+ </goals>
+ <configuration>
+ <keepSources>true</keepSources>
+ <useProvidedScope>true</useProvidedScope>
+ <excludes>
+ **/canvas.jsp
+ </excludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>net.alchim31.maven</groupId>
+ <artifactId>yuicompressor-maven-plugin</artifactId>
+ <version>1.5.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>compress</goal>
+ </goals>
+ <configuration>
+ <sourceDirectory>src/main/webapp</sourceDirectory>
+ <outputDirectory>${staging.dir}</outputDirectory>
+ <nomunge>false</nomunge>
+ <jswarn>false</jswarn>
+ <nosuffix>true</nosuffix>
+ <gzip>true</gzip>
+ <aggregations>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-ng-breadcrumbs-controller-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-ng-processor-component-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/header/components/nf-ng-processor-component.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-connection-configuration-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/nf-connection-configuration.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-flow-version-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/nf-flow-version.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-process-group-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/nf-process-group.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}//nf-settings-min.js</output>
+ <includes>
+ <include>${staging.dir}/js/nf/canvas/nf-settings.js</include>
+ </includes>
+ </aggregation>
+ <aggregation>
+ <insertNewLine>true</insertNewLine>
+ <output>${project.build.directory}/${project.build.finalName}/navigation-min.css</output>
+ <includes>
+ <include>${staging.dir}/css/navigation.css</include>
+ </includes>
+ </aggregation>
+ </aggregations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>3.2.1</version>
+ <configuration>
+ <webResources>
+ <resource>
+ <directory>src/main/webapp/WEB-INF/pages</directory>
+ <targetPath>WEB-INF/pages</targetPath>
+ <includes>
+ <include>canvas.jsp</include>
+ </includes>
+ <filtering>true</filtering>
+ </resource>
+ </webResources>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>${docker.fabric.version}</version>
+ <configuration>
+ <verbose>true</verbose>
+ <pullRegistry>${docker.pull.registry}</pullRegistry>
+ <pushRegistry>${docker.push.registry}</pushRegistry>
+ <images>
+ <image>
+ <name>onap/${project.groupId}.${project.artifactId}</name>
+ <registry>${onap.nexus.dockerregistry.daily}</registry>
+ <build>
+ <contextDir>${project.basedir}</contextDir>
+ <tags>
+ <tag>latest</tag>
+ <tag>${project.version}</tag>
+ <tag>${project.version}-${maven.build.timestamp}Z</tag>
+ </tags>
+ </build>
+ </image>
+ </images>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>build</goal>
+ <goal>push</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/mod/designtool/designtool-web/sh/applypatches.sh b/mod/designtool/designtool-web/sh/applypatches.sh
new file mode 100755
index 0000000..47de89c
--- /dev/null
+++ b/mod/designtool/designtool-web/sh/applypatches.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+# ============LICENSE_START=====================================================
+# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+# ==============================================================================
+# 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.
+# ============LICENSE_END=======================================================
+
+set -eufx -o pipefail
+
+PATCH_BINARY=/tmp/patches/designtool-web-$1.war
+NIFI_VERSION=$2
+PATCHES=/tmp/patches
+TARGETS=/tmp/targets
+mkdir -p $PATCHES $TARGETS
+# extract patches
+cd $PATCHES
+jar xf $PATCH_BINARY
+rm $PATCH_BINARY
+# extract jars and wars to be patched
+cd $TARGETS
+jar xf $NIFI_BASE_DIR/nifi-current/lib/nifi-framework-nar-$NIFI_VERSION.nar \
+ META-INF/bundled-dependencies/nifi-framework-nar-loading-utils-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-jetty-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-web-api-$NIFI_VERSION.war \
+ META-INF/bundled-dependencies/nifi-web-ui-$NIFI_VERSION.war
+# patch jar files
+cd $PATCHES/WEB-INF/classes
+set +f
+jar uf $NIFI_BASE_DIR/nifi-toolkit-current/lib/nifi-client-dto-$NIFI_VERSION.jar \
+ org/apache/nifi/web/api/dto/FlowConfigurationDTO*.class
+jar uf $TARGETS/META-INF/bundled-dependencies/nifi-jetty-$NIFI_VERSION.jar \
+ org/apache/nifi/web/server/JettyServer*.class
+jar uf $NIFI_BASE_DIR/nifi-current/lib/nifi-properties-$NIFI_VERSION.jar \
+ org/apache/nifi/util/NiFiProperties*.class
+jar uf $NIFI_BASE_DIR/nifi-current/lib/nifi-runtime-$NIFI_VERSION.jar \
+ org/apache/nifi/NiFi*.class
+jar uf $NIFI_BASE_DIR/nifi-toolkit-current/lib/nifi-framework-core-api-$NIFI_VERSION.jar \
+ org/apache/nifi/controller/AbstractPort*.class
+jar uf $TARGETS/META-INF/bundled-dependencies/nifi-framework-nar-loading-utils-$NIFI_VERSION.jar \
+ org/apache/nifi/nar/DCAEClassLoaders*.class \
+ org/apache/nifi/nar/DCAEAutoLoader*.class
+# patch war files
+cd $PATCHES
+jar uf $TARGETS/META-INF/bundled-dependencies/nifi-web-api-$NIFI_VERSION.war \
+ WEB-INF/classes/org/apache/nifi/web/StandardNiFiServiceFacade*.class \
+ WEB-INF/classes/org/apache/nifi/web/api/dto/DtoFactory*.class \
+ WEB-INF/classes/org/apache/nifi/web/dao/impl/StandardConnectionDAO*.class
+set -f
+jar xf $TARGETS/META-INF/bundled-dependencies/nifi-web-ui-$NIFI_VERSION.war \
+ css/nf-canvas-all.css \
+ js/nf/canvas/nf-canvas-all.js \
+ js/nf/summary/nf-summary-all.js
+rm -f \
+ css/nf-canvas-all.css.gz \
+ js/nf/canvas/nf-canvas-all.js.gz \
+ js/nf/summary/nf-summary-all.js.gz
+sed -i \
+ -e '/graph-controls/{r navigation-min.css' -e 'd}' \
+ css/nf-canvas-all.css
+sed -i \
+ -e '/process-group-up-to-date/{r nf-process-group-min.js' -e 'd}' \
+ -e '/div.available-relationship/{r nf-connection-configuration-min.js' -e 'd}' \
+ -e '/nf.FlowVerison/{r nf-flow-version-min.js' -e 'd}' \
+ -e '/controllerConfig/{r nf-settings-min.js' -e 'd}' \
+ -e '/this.breadcrumbs/{r nf-ng-breadcrumbs-controller-min.js' -e 'd}' \
+ -e '/processor-types-table/{r nf-ng-processor-component-min.js' -e 'd}' \
+ js/nf/canvas/nf-canvas-all.js
+sed -i \
+ -e '/controllerConfig/{r nf-settings-min.js' -e 'd}' \
+ js/nf/summary/nf-summary-all.js
+gzip -k \
+ css/nf-canvas-all.css \
+ js/nf/canvas/nf-canvas-all.js \
+ js/nf/summary/nf-summary-all.js
+jar uf $TARGETS/META-INF/bundled-dependencies/nifi-web-ui-$NIFI_VERSION.war \
+ $(find WEB-INF/classes/org/apache/jsp/WEB_002dINF WEB-INF/pages WEB-INF/partials css js images -type f -print)
+# patch scripts
+cp common.sh start.sh $NIFI_BASE_DIR/scripts/
+# patch nar files
+cd $TARGETS
+cp $NIFI_BASE_DIR/nifi-toolkit-current/lib/nifi-client-dto-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-client-dto-$NIFI_VERSION.jar
+jar uf $NIFI_BASE_DIR/nifi-current/lib/nifi-site-to-site-reporting-nar-$NIFI_VERSION.nar \
+ META-INF/bundled-dependencies/nifi-client-dto-$NIFI_VERSION.jar
+cp $NIFI_BASE_DIR/nifi-toolkit-current/lib/nifi-framework-core-api-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-framework-core-api-$NIFI_VERSION.jar
+jar uf $NIFI_BASE_DIR/nifi-current/lib/nifi-framework-nar-$NIFI_VERSION.nar \
+ META-INF/bundled-dependencies/nifi-client-dto-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-framework-core-api-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-framework-nar-loading-utils-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-jetty-$NIFI_VERSION.jar \
+ META-INF/bundled-dependencies/nifi-web-api-$NIFI_VERSION.war \
+ META-INF/bundled-dependencies/nifi-web-ui-$NIFI_VERSION.war
+cp $NIFI_BASE_DIR/nifi-current/lib/nifi-properties-$NIFI_VERSION.jar \
+ $NIFI_BASE_DIR/nifi-toolkit-current/lib/nifi-properties-$NIFI_VERSION.jar
+echo Success
+exit 0
diff --git a/mod/designtool/designtool-web/sh/common.sh b/mod/designtool/designtool-web/sh/common.sh
new file mode 100755
index 0000000..542b777
--- /dev/null
+++ b/mod/designtool/designtool-web/sh/common.sh
@@ -0,0 +1,43 @@
+#!/bin/sh -e
+# 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.
+
+#
+# Modifications to the original nifi code for the ONAP project are made
+# available under the Apache License, Version 2.0
+#
+
+# 1 - value to search for
+# 2 - value to replace
+# 3 - file to perform replacement inline
+prop_replace () {
+ target_file=${3:-${nifi_props_file}}
+ echo 'replacing target file ' ${target_file}
+ sed -i -e "s|^$1=.*$|$1=$2|" ${target_file}
+}
+
+# 1 - property name
+# 2 - property value
+# 3 - file to perform replacement inline
+prop_append () {
+ target_file=${3:-${nifi_props_file}}
+ echo 'appending target file ' ${target_file}
+ sed -i "$ a $1=$2" ${target_file}
+}
+
+# NIFI_HOME is defined by an ENV command in the backing Dockerfile
+export nifi_props_file=${NIFI_HOME}/conf/nifi.properties
+export nifi_toolkit_props_file=${HOME}/.nifi-cli.nifi.properties
+export hostname=$(hostname)
diff --git a/mod/designtool/designtool-web/sh/start.sh b/mod/designtool/designtool-web/sh/start.sh
new file mode 100755
index 0000000..8658983
--- /dev/null
+++ b/mod/designtool/designtool-web/sh/start.sh
@@ -0,0 +1,97 @@
+#!/bin/sh -e
+
+# 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.
+
+#
+# Modifications to the original nifi code for the ONAP project are made
+# available under the Apache License, Version 2.0
+#
+
+scripts_dir='/opt/nifi/scripts'
+
+[ -f "${scripts_dir}/common.sh" ] && . "${scripts_dir}/common.sh"
+
+# Establish baseline properties
+prop_replace 'nifi.web.http.port' "${NIFI_WEB_HTTP_PORT:-8080}"
+prop_replace 'nifi.web.http.host' "${NIFI_WEB_HTTP_HOST:-$HOSTNAME}"
+prop_replace 'nifi.remote.input.host' "${NIFI_REMOTE_INPUT_HOST:-$HOSTNAME}"
+prop_replace 'nifi.remote.input.socket.port' "${NIFI_REMOTE_INPUT_SOCKET_PORT:-10000}"
+prop_replace 'nifi.remote.input.secure' 'false'
+
+# Set nifi-toolkit properties files and baseUrl
+"${scripts_dir}/toolkit.sh"
+prop_replace 'baseUrl' "http://${NIFI_WEB_HTTP_HOST:-$HOSTNAME}:${NIFI_WEB_HTTP_PORT:-8080}" ${nifi_toolkit_props_file}
+
+prop_replace 'nifi.variable.registry.properties' "${NIFI_VARIABLE_REGISTRY_PROPERTIES:-}"
+prop_replace 'nifi.cluster.is.node' "${NIFI_CLUSTER_IS_NODE:-false}"
+prop_replace 'nifi.cluster.node.address' "${NIFI_CLUSTER_ADDRESS:-$HOSTNAME}"
+prop_replace 'nifi.cluster.node.protocol.port' "${NIFI_CLUSTER_NODE_PROTOCOL_PORT:-}"
+prop_replace 'nifi.cluster.node.protocol.threads' "${NIFI_CLUSTER_NODE_PROTOCOL_THREADS:-10}"
+prop_replace 'nifi.cluster.node.protocol.max.threads' "${NIFI_CLUSTER_NODE_PROTOCOL_MAX_THREADS:-50}"
+prop_replace 'nifi.zookeeper.connect.string' "${NIFI_ZK_CONNECT_STRING:-}"
+prop_replace 'nifi.zookeeper.root.node' "${NIFI_ZK_ROOT_NODE:-/nifi}"
+prop_replace 'nifi.cluster.flow.election.max.wait.time' "${NIFI_ELECTION_MAX_WAIT:-5 mins}"
+prop_replace 'nifi.cluster.flow.election.max.candidates' "${NIFI_ELECTION_MAX_CANDIDATES:-}"
+prop_replace 'nifi.web.proxy.context.path' "${NIFI_WEB_PROXY_CONTEXT_PATH:-}"
+
+# REVIEW: Could not figure out how the nifi.properties file gets generated so
+# replace value conditionally if the property name exists otherwise append
+if grep -q 'nifi.dcae.jars.index.url' $nifi_props_file
+then
+ prop_replace 'nifi.dcae.jars.index.url' "${NIFI_DCAE_JARS_INDEX_URL:-http://genprocessor-http/nifi-jars/}"
+else
+ prop_append 'nifi.dcae.jars.index.url' "${NIFI_DCAE_JARS_INDEX_URL:-http://genprocessor-http/nifi-jars/}"
+fi
+
+if grep -q 'nifi.ui.dcae.distibutor.api.url' $nifi_props_file
+then
+ prop_replace 'nifi.ui.dcae.distibutor.api.url' "${NIFI_DCAE_DISTRIBUTOR_API_URL:-http://distributor-api}"
+else
+ prop_append 'nifi.ui.dcae.distibutor.api.url' "${NIFI_DCAE_DISTRIBUTOR_API_URL:-http://distributor-api}"
+fi
+
+. "${scripts_dir}/update_cluster_state_management.sh"
+
+# Check if we are secured or unsecured
+case ${AUTH} in
+ tls)
+ echo 'Enabling Two-Way SSL user authentication'
+ . "${scripts_dir}/secure.sh"
+ ;;
+ ldap)
+ echo 'Enabling LDAP user authentication'
+ # Reference ldap-provider in properties
+ prop_replace 'nifi.security.user.login.identity.provider' 'ldap-provider'
+
+ . "${scripts_dir}/secure.sh"
+ . "${scripts_dir}/update_login_providers.sh"
+ ;;
+ *)
+ if [ ! -z "${NIFI_WEB_PROXY_HOST}" ]; then
+ echo 'NIFI_WEB_PROXY_HOST was set but NiFi is not configured to run in a secure mode. Will not update nifi.web.proxy.host.'
+ fi
+ ;;
+esac
+
+# Continuously provide logs so that 'docker logs' can produce them
+tail -F "${NIFI_HOME}/logs/nifi-app.log" &
+"${NIFI_HOME}/bin/nifi.sh" run &
+nifi_pid="$!"
+
+trap "echo Received trapped signal, beginning shutdown...;" KILL TERM HUP INT EXIT;
+
+echo NiFi running with PID ${nifi_pid}.
+wait ${nifi_pid}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/NiFi.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/NiFi.java
new file mode 100644
index 0000000..0b033db
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/NiFi.java
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.nar.NarClassLoaders;
+import org.apache.nifi.nar.NarClassLoadersHolder;
+import org.apache.nifi.nar.NarUnpacker;
+import org.apache.nifi.nar.SystemBundle;
+import org.apache.nifi.util.FileUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class NiFi {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class);
+ private static final String KEY_FILE_FLAG = "-K";
+ private final NiFiServer nifiServer;
+ private final BootstrapListener bootstrapListener;
+
+ public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.bootstrap.listen.port";
+ private volatile boolean shutdown = false;
+
+ public NiFi(final NiFiProperties properties)
+ throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ this(properties, ClassLoader.getSystemClassLoader());
+
+ }
+
+ public NiFi(final NiFiProperties properties, ClassLoader rootClassLoader)
+ throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ // There can only be one krb5.conf for the overall Java process so set this globally during
+ // start up so that processors and our Kerberos authentication code don't have to set this
+ final File kerberosConfigFile = properties.getKerberosConfigurationFile();
+ if (kerberosConfigFile != null) {
+ final String kerberosConfigFilePath = kerberosConfigFile.getAbsolutePath();
+ LOGGER.info("Setting java.security.krb5.conf to {}", new Object[]{kerberosConfigFilePath});
+ System.setProperty("java.security.krb5.conf", kerberosConfigFilePath);
+ }
+
+ setDefaultUncaughtExceptionHandler();
+
+ // register the shutdown hook
+ addShutdownHook();
+
+ final String bootstrapPort = System.getProperty(BOOTSTRAP_PORT_PROPERTY);
+ if (bootstrapPort != null) {
+ try {
+ final int port = Integer.parseInt(bootstrapPort);
+
+ if (port < 1 || port > 65535) {
+ throw new RuntimeException("Failed to start NiFi because system property '" + BOOTSTRAP_PORT_PROPERTY + "' is not a valid integer in the range 1 - 65535");
+ }
+
+ bootstrapListener = new BootstrapListener(this, port);
+ bootstrapListener.start();
+ } catch (final NumberFormatException nfe) {
+ throw new RuntimeException("Failed to start NiFi because system property '" + BOOTSTRAP_PORT_PROPERTY + "' is not a valid integer in the range 1 - 65535");
+ }
+ } else {
+ LOGGER.info("NiFi started without Bootstrap Port information provided; will not listen for requests from Bootstrap");
+ bootstrapListener = null;
+ }
+
+ // delete the web working dir - if the application does not start successfully
+ // the web app directories might be in an invalid state. when this happens
+ // jetty will not attempt to re-extract the war into the directory. by removing
+ // the working directory, we can be assured that it will attempt to extract the
+ // war every time the application starts.
+ File webWorkingDir = properties.getWebWorkingDirectory();
+ FileUtils.deleteFilesInDirectory(webWorkingDir, null, LOGGER, true, true);
+ FileUtils.deleteFile(webWorkingDir, LOGGER, 3);
+
+ detectTimingIssues();
+
+ // redirect JUL log events
+ initLogging();
+
+ final Bundle systemBundle = SystemBundle.create(properties);
+
+ // expand the nars
+ final ExtensionMapping extensionMapping = NarUnpacker.unpackNars(properties, systemBundle);
+
+ // load the extensions classloaders
+ NarClassLoaders narClassLoaders = NarClassLoadersHolder.getInstance();
+
+ narClassLoaders.init(rootClassLoader,
+ properties.getFrameworkWorkingDirectory(), properties.getExtensionsWorkingDirectory());
+
+ // load the framework classloader
+ final ClassLoader frameworkClassLoader = narClassLoaders.getFrameworkBundle().getClassLoader();
+ if (frameworkClassLoader == null) {
+ throw new IllegalStateException("Unable to find the framework NAR ClassLoader.");
+ }
+
+ final Set<Bundle> narBundles = narClassLoaders.getBundles();
+
+ // load the server from the framework classloader
+ Thread.currentThread().setContextClassLoader(frameworkClassLoader);
+ Class<?> jettyServer = Class.forName("org.apache.nifi.web.server.JettyServer", true, frameworkClassLoader);
+ Constructor<?> jettyConstructor = jettyServer.getConstructor(NiFiProperties.class, Set.class);
+
+ final long startTime = System.nanoTime();
+ nifiServer = (NiFiServer) jettyConstructor.newInstance(properties, narBundles);
+ nifiServer.setExtensionMapping(extensionMapping);
+ nifiServer.setBundles(systemBundle, narBundles);
+
+ if (shutdown) {
+ LOGGER.info("NiFi has been shutdown via NiFi Bootstrap. Will not start Controller");
+ } else {
+ nifiServer.start();
+
+ if (bootstrapListener != null) {
+ bootstrapListener.sendStartedStatus(true);
+ }
+
+ final long duration = System.nanoTime() - startTime;
+ LOGGER.info("Controller initialization took " + duration + " nanoseconds "
+ + "(" + (int) TimeUnit.SECONDS.convert(duration, TimeUnit.NANOSECONDS) + " seconds).");
+ }
+ }
+
+ protected void setDefaultUncaughtExceptionHandler() {
+ Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(final Thread t, final Throwable e) {
+ LOGGER.error("An Unknown Error Occurred in Thread {}: {}", t, e.toString());
+ LOGGER.error("", e);
+ }
+ });
+ }
+
+ protected void addShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // shutdown the jetty server
+ shutdownHook();
+ }
+ }));
+ }
+
+ protected void initLogging() {
+ SLF4JBridgeHandler.removeHandlersForRootLogger();
+ SLF4JBridgeHandler.install();
+ }
+
+ private static ClassLoader createBootstrapClassLoader() {
+ //Get list of files in bootstrap folder
+ final List<URL> urls = new ArrayList<>();
+ try {
+ Files.list(Paths.get("lib/bootstrap")).forEach(p -> {
+ try {
+ urls.add(p.toUri().toURL());
+ } catch (final MalformedURLException mef) {
+ LOGGER.warn("Unable to load " + p.getFileName() + " due to " + mef, mef);
+ }
+ });
+ } catch (IOException ioe) {
+ LOGGER.warn("Unable to access lib/bootstrap to create bootstrap classloader", ioe);
+ }
+ //Create the bootstrap classloader
+ return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
+ }
+
+ protected void shutdownHook() {
+ try {
+ shutdown();
+ } catch (final Throwable t) {
+ LOGGER.warn("Problem occurred ensuring Jetty web server was properly terminated due to " + t);
+ }
+ }
+
+ protected void shutdown() {
+ this.shutdown = true;
+
+ LOGGER.info("Initiating shutdown of Jetty web server...");
+ if (nifiServer != null) {
+ nifiServer.stop();
+ }
+ if (bootstrapListener != null) {
+ bootstrapListener.stop();
+ }
+ LOGGER.info("Jetty web server shutdown completed (nicely or otherwise).");
+ }
+
+ /**
+ * Determine if the machine we're running on has timing issues.
+ */
+ private void detectTimingIssues() {
+ final int minRequiredOccurrences = 25;
+ final int maxOccurrencesOutOfRange = 15;
+ final AtomicLong lastTriggerMillis = new AtomicLong(System.currentTimeMillis());
+
+ final ScheduledExecutorService service = Executors.newScheduledThreadPool(1, new ThreadFactory() {
+ private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ final Thread t = defaultFactory.newThread(r);
+ t.setDaemon(true);
+ t.setName("Detect Timing Issues");
+ return t;
+ }
+ });
+
+ final AtomicInteger occurrencesOutOfRange = new AtomicInteger(0);
+ final AtomicInteger occurrences = new AtomicInteger(0);
+ final Runnable command = new Runnable() {
+ @Override
+ public void run() {
+ final long curMillis = System.currentTimeMillis();
+ final long difference = curMillis - lastTriggerMillis.get();
+ final long millisOff = Math.abs(difference - 2000L);
+ occurrences.incrementAndGet();
+ if (millisOff > 500L) {
+ occurrencesOutOfRange.incrementAndGet();
+ }
+ lastTriggerMillis.set(curMillis);
+ }
+ };
+
+ final ScheduledFuture<?> future = service.scheduleWithFixedDelay(command, 2000L, 2000L, TimeUnit.MILLISECONDS);
+
+ final TimerTask timerTask = new TimerTask() {
+ @Override
+ public void run() {
+ future.cancel(true);
+ service.shutdownNow();
+
+ if (occurrences.get() < minRequiredOccurrences || occurrencesOutOfRange.get() > maxOccurrencesOutOfRange) {
+ LOGGER.warn("NiFi has detected that this box is not responding within the expected timing interval, which may cause "
+ + "Processors to be scheduled erratically. Please see the NiFi documentation for more information.");
+ }
+ }
+ };
+ final Timer timer = new Timer(true);
+ timer.schedule(timerTask, 60000L);
+ }
+
+ /**
+ * Main entry point of the application.
+ *
+ * @param args things which are ignored
+ */
+ public static void main(String[] args) {
+ LOGGER.info("Launching NiFi...");
+ try {
+ NiFiProperties properties = convertArgumentsToValidatedNiFiProperties(args);
+ new NiFi(properties);
+ } catch (final Throwable t) {
+ LOGGER.error("Failure to launch NiFi due to " + t, t);
+ }
+ }
+
+ protected static NiFiProperties convertArgumentsToValidatedNiFiProperties(String[] args) {
+ final ClassLoader bootstrap = createBootstrapClassLoader();
+ NiFiProperties properties = initializeProperties(args, bootstrap);
+ properties.validate();
+ return properties;
+ }
+
+ private static NiFiProperties initializeProperties(final String[] args, final ClassLoader boostrapLoader) {
+ // Try to get key
+ // If key doesn't exist, instantiate without
+ // Load properties
+ // If properties are protected and key missing, throw RuntimeException
+
+ final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ final String key;
+ try {
+ key = loadFormattedKey(args);
+ // The key might be empty or null when it is passed to the loader
+ } catch (IllegalArgumentException e) {
+ final String msg = "The bootstrap process did not provide a valid key";
+ throw new IllegalArgumentException(msg, e);
+ }
+ Thread.currentThread().setContextClassLoader(boostrapLoader);
+
+ try {
+ final Class<?> propsLoaderClass = Class.forName("org.apache.nifi.properties.NiFiPropertiesLoader", true, boostrapLoader);
+ final Method withKeyMethod = propsLoaderClass.getMethod("withKey", String.class);
+ final Object loaderInstance = withKeyMethod.invoke(null, key);
+ final Method getMethod = propsLoaderClass.getMethod("get");
+ final NiFiProperties properties = (NiFiProperties) getMethod.invoke(loaderInstance);
+ LOGGER.info("Loaded {} properties", properties.size());
+ return properties;
+ } catch (InvocationTargetException wrappedException) {
+ final String msg = "There was an issue decrypting protected properties";
+ throw new IllegalArgumentException(msg, wrappedException.getCause() == null ? wrappedException : wrappedException.getCause());
+ } catch (final IllegalAccessException | NoSuchMethodException | ClassNotFoundException reex) {
+ final String msg = "Unable to access properties loader in the expected manner - apparent classpath or build issue";
+ throw new IllegalArgumentException(msg, reex);
+ } catch (final RuntimeException e) {
+ final String msg = "There was an issue decrypting protected properties";
+ throw new IllegalArgumentException(msg, e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+
+ private static String loadFormattedKey(String[] args) {
+ String key = null;
+ List<String> parsedArgs = parseArgs(args);
+ // Check if args contain protection key
+ if (parsedArgs.contains(KEY_FILE_FLAG)) {
+ key = getKeyFromKeyFileAndPrune(parsedArgs);
+ // Format the key (check hex validity and remove spaces)
+ key = formatHexKey(key);
+
+ }
+
+ if (null == key) {
+ return "";
+ } else if (!isHexKeyValid(key)) {
+ throw new IllegalArgumentException("The key was not provided in valid hex format and of the correct length");
+ } else {
+ return key;
+ }
+ }
+
+ private static String getKeyFromKeyFileAndPrune(List<String> parsedArgs) {
+ String key = null;
+ LOGGER.debug("The bootstrap process provided the " + KEY_FILE_FLAG + " flag");
+ int i = parsedArgs.indexOf(KEY_FILE_FLAG);
+ if (parsedArgs.size() <= i + 1) {
+ LOGGER.error("The bootstrap process passed the {} flag without a filename", KEY_FILE_FLAG);
+ throw new IllegalArgumentException("The bootstrap process provided the " + KEY_FILE_FLAG + " flag but no key");
+ }
+ try {
+ String passwordfile_path = parsedArgs.get(i + 1);
+ // Slurp in the contents of the file:
+ byte[] encoded = Files.readAllBytes(Paths.get(passwordfile_path));
+ key = new String(encoded,StandardCharsets.UTF_8);
+ if (0 == key.length())
+ throw new IllegalArgumentException("Key in keyfile " + passwordfile_path + " yielded an empty key");
+
+ LOGGER.info("Now overwriting file in "+passwordfile_path);
+
+ // Overwrite the contents of the file (to avoid littering file system
+ // unlinked with key material):
+ File password_file = new File(passwordfile_path);
+ FileWriter overwriter = new FileWriter(password_file,false);
+
+ // Construe a random pad:
+ Random r = new Random();
+ StringBuffer sb = new StringBuffer();
+ // Note on correctness: this pad is longer, but equally sufficient.
+ while(sb.length() < encoded.length){
+ sb.append(Integer.toHexString(r.nextInt()));
+ }
+ String pad = sb.toString();
+ LOGGER.info("Overwriting key material with pad: "+pad);
+ overwriter.write(pad);
+ overwriter.close();
+
+ LOGGER.info("Removing/unlinking file: "+passwordfile_path);
+ password_file.delete();
+
+ } catch (IOException e) {
+ LOGGER.error("Caught IOException while retrieving the "+KEY_FILE_FLAG+"-passed keyfile; aborting: "+e.toString());
+ System.exit(1);
+ }
+
+ LOGGER.info("Read property protection key from key file provided by bootstrap process");
+ return key;
+ }
+
+ private static List<String> parseArgs(String[] args) {
+ List<String> parsedArgs = new ArrayList<>(Arrays.asList(args));
+ for (int i = 0; i < parsedArgs.size(); i++) {
+ if (parsedArgs.get(i).startsWith(KEY_FILE_FLAG + " ")) {
+ String[] split = parsedArgs.get(i).split(" ", 2);
+ parsedArgs.set(i, split[0]);
+ parsedArgs.add(i + 1, split[1]);
+ break;
+ }
+ }
+ return parsedArgs;
+ }
+
+ private static String formatHexKey(String input) {
+ if (input == null || input.trim().isEmpty()) {
+ return "";
+ }
+ return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
+ }
+
+ private static boolean isHexKeyValid(String key) {
+ if (key == null || key.trim().isEmpty()) {
+ return false;
+ }
+ // Key length is in "nibbles" (i.e. one hex char = 4 bits)
+ return Arrays.asList(128, 196, 256).contains(key.length() * 4) && key.matches("^[0-9a-fA-F]*$");
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/controller/AbstractPort.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/controller/AbstractPort.java
new file mode 100644
index 0000000..6023fc2
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/controller/AbstractPort.java
@@ -0,0 +1,675 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.controller;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.ResourceFactory;
+import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.ConnectableType;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.Port;
+import org.apache.nifi.connectable.Position;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.ProcessSessionFactory;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.util.CharacterFilterUtils;
+import org.apache.nifi.util.FormatUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class AbstractPort implements Port {
+
+ public static final Relationship PORT_RELATIONSHIP = new Relationship.Builder()
+ .description("The relationship through which all Flow Files are transferred")
+ .name("")
+ .build();
+
+ public static final long MINIMUM_PENALIZATION_MILLIS = 0L;
+ public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ public static final long MINIMUM_YIELD_MILLIS = 0L;
+ public static final long DEFAULT_YIELD_PERIOD = 10000L;
+ public static final TimeUnit DEFAULT_YIELD_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ private final List<Relationship> relationships;
+
+ private final String id;
+ private final ConnectableType type;
+ private final AtomicReference<String> name;
+ private final AtomicReference<Position> position;
+ private final AtomicReference<String> comments;
+ private final AtomicReference<ProcessGroup> processGroup;
+ private final AtomicBoolean lossTolerant;
+ private final AtomicReference<ScheduledState> scheduledState;
+ private final AtomicInteger concurrentTaskCount;
+ private final AtomicReference<String> penalizationPeriod;
+ private final AtomicReference<String> yieldPeriod;
+ private final AtomicReference<String> schedulingPeriod;
+ private final AtomicReference<String> versionedComponentId = new AtomicReference<>();
+ private final AtomicLong schedulingNanos;
+ private final AtomicLong yieldExpiration;
+ private final ProcessScheduler processScheduler;
+
+ private final Set<Connection> outgoingConnections;
+ private final List<Connection> incomingConnections;
+
+ private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
+ private final Lock readLock = rwLock.readLock();
+ private final Lock writeLock = rwLock.writeLock();
+
+ public AbstractPort(final String id, final String name, final ProcessGroup processGroup, final ConnectableType type, final ProcessScheduler scheduler) {
+ this.id = requireNonNull(id);
+ this.name = new AtomicReference<>(requireNonNull(name));
+ position = new AtomicReference<>(new Position(0D, 0D));
+ outgoingConnections = new HashSet<>();
+ incomingConnections = new ArrayList<>();
+ comments = new AtomicReference<>();
+ lossTolerant = new AtomicBoolean(false);
+ concurrentTaskCount = new AtomicInteger(1);
+ processScheduler = scheduler;
+
+ final List<Relationship> relationshipList = new ArrayList<>();
+ relationshipList.add(PORT_RELATIONSHIP);
+ relationships = Collections.unmodifiableList(relationshipList);
+ this.processGroup = new AtomicReference<>(processGroup);
+ this.type = type;
+ penalizationPeriod = new AtomicReference<>("30 sec");
+ yieldPeriod = new AtomicReference<>("1 sec");
+ yieldExpiration = new AtomicLong(0L);
+ schedulingPeriod = new AtomicReference<>("0 millis");
+ schedulingNanos = new AtomicLong(MINIMUM_SCHEDULING_NANOS);
+ scheduledState = new AtomicReference<>(ScheduledState.STOPPED);
+ }
+
+ @Override
+ public String getIdentifier() {
+ return id;
+ }
+
+ @Override
+ public String getProcessGroupIdentifier() {
+ final ProcessGroup procGroup = getProcessGroup();
+ return procGroup == null ? null : procGroup.getIdentifier();
+ }
+
+ @Override
+ public String getName() {
+ return name.get();
+ }
+
+ @Override
+ public void setName(final String name) {
+ if (this.name.get().equals(name)) {
+ return;
+ }
+
+ final ProcessGroup parentGroup = this.processGroup.get();
+ if (getConnectableType() == ConnectableType.INPUT_PORT) {
+ if (parentGroup.getInputPortByName(name) != null) {
+ throw new IllegalStateException("The requested new port name is not available");
+ }
+ } else if (getConnectableType() == ConnectableType.OUTPUT_PORT) {
+ if (parentGroup.getOutputPortByName(name) != null) {
+ throw new IllegalStateException("The requested new port name is not available");
+ }
+ }
+
+ this.name.set(name);
+ }
+
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return getProcessGroup();
+ }
+
+ @Override
+ public Resource getResource() {
+ final ResourceType resourceType = ConnectableType.INPUT_PORT.equals(getConnectableType()) ? ResourceType.InputPort : ResourceType.OutputPort;
+ return ResourceFactory.getComponentResource(resourceType, getIdentifier(), getName());
+ }
+
+ @Override
+ public ProcessGroup getProcessGroup() {
+ return processGroup.get();
+ }
+
+ @Override
+ public void setProcessGroup(final ProcessGroup newGroup) {
+ this.processGroup.set(newGroup);
+ }
+
+ @Override
+ public String getComments() {
+ return comments.get();
+ }
+
+ @Override
+ public void setComments(final String comments) {
+ this.comments.set(CharacterFilterUtils.filterInvalidXmlCharacters(comments));
+ }
+
+ @Override
+ public Collection<Relationship> getRelationships() {
+ return relationships;
+ }
+
+ @Override
+ public Relationship getRelationship(final String relationshipName) {
+ if (PORT_RELATIONSHIP.getName().equals(relationshipName)) {
+ return PORT_RELATIONSHIP;
+ }
+ return null;
+ }
+
+ @Override
+ public void addConnection(final Connection connection) throws IllegalArgumentException {
+ writeLock.lock();
+ try {
+ if (!requireNonNull(connection).getSource().equals(this)) {
+ if (connection.getDestination().equals(this)) {
+ // don't add the connection twice. This may occur if we have a self-loop because we will be told
+ // to add the connection once because we are the source and again because we are the destination.
+ if (!incomingConnections.contains(connection)) {
+ incomingConnections.add(connection);
+ }
+
+ return;
+ } else {
+ throw new IllegalArgumentException("Cannot add a connection to a LocalPort for which the LocalPort is neither the Source nor the Destination");
+ }
+ }
+
+ /* TODO: Will commenting this out have repercussions?
+ Needed to comment this out to allow use of relationships for port to processor case which was previously not supported
+ for (final Relationship relationship : connection.getRelationships()) {
+ if (!relationship.equals(PORT_RELATIONSHIP)) {
+ throw new IllegalArgumentException("No relationship with name " + relationship + " exists for Local Ports");
+ }
+ }
+ */
+
+ // don't add the connection twice. This may occur if we have a self-loop because we will be told
+ // to add the connection once because we are the source and again because we are the destination.
+ if (!outgoingConnections.contains(connection)) {
+ outgoingConnections.add(connection);
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public boolean hasIncomingConnection() {
+ readLock.lock();
+ try {
+ return !incomingConnections.isEmpty();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void onTrigger(final ProcessContext context, final ProcessSessionFactory sessionFactory) throws ProcessException {
+ final ProcessSession session = sessionFactory.createSession();
+
+ try {
+ onTrigger(context, session);
+ session.commit();
+ } catch (final ProcessException e) {
+ session.rollback();
+ throw e;
+ } catch (final Throwable t) {
+ session.rollback();
+ throw new RuntimeException(t);
+ }
+ }
+
+ public abstract void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException;
+
+ @Override
+ public void updateConnection(final Connection connection) throws IllegalStateException {
+ if (requireNonNull(connection).getSource().equals(this)) {
+ writeLock.lock();
+ try {
+ if (!outgoingConnections.remove(connection)) {
+ throw new IllegalStateException("No Connection with ID " + connection.getIdentifier() + " is currently registered with this Port");
+ }
+ outgoingConnections.add(connection);
+ } finally {
+ writeLock.unlock();
+ }
+ } else if (connection.getDestination().equals(this)) {
+ writeLock.lock();
+ try {
+ if (!incomingConnections.remove(connection)) {
+ throw new IllegalStateException("No Connection with ID " + connection.getIdentifier() + " is currently registered with this Port");
+ }
+ incomingConnections.add(connection);
+ } finally {
+ writeLock.unlock();
+ }
+ } else {
+ throw new IllegalStateException("The given connection is not currently registered for this Port");
+ }
+ }
+
+ @Override
+ public void removeConnection(final Connection connection) throws IllegalArgumentException, IllegalStateException {
+ writeLock.lock();
+ try {
+ if (!requireNonNull(connection).getSource().equals(this)) {
+ final boolean existed = incomingConnections.remove(connection);
+ if (!existed) {
+ throw new IllegalStateException("The given connection is not currently registered for this Port");
+ }
+ return;
+ }
+
+ if (!canConnectionBeRemoved(connection)) {
+ // TODO: Determine which processors will be broken if connection is removed, rather than just returning a boolean
+ throw new IllegalStateException("Connection " + connection.getIdentifier() + " cannot be removed");
+ }
+
+ final boolean removed = outgoingConnections.remove(connection);
+ if (!removed) {
+ throw new IllegalStateException("Connection " + connection.getIdentifier() + " is not registered with " + this.getIdentifier());
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * Verify that removing this connection will not prevent this Port from
+ * still being connected via each relationship
+ *
+ * @param connection to test for removal
+ * @return true if can be removed
+ */
+ private boolean canConnectionBeRemoved(final Connection connection) {
+ final Connectable source = connection.getSource();
+ if (!source.isRunning()) {
+ // we don't have to verify that this Connectable is still connected because it's okay to make
+ // the source invalid since it is not running.
+ return true;
+ }
+
+ for (final Relationship relationship : source.getRelationships()) {
+ if (source.isAutoTerminated(relationship)) {
+ continue;
+ }
+
+ final Set<Connection> connectionsForRelationship = source.getConnections(relationship);
+ if (connectionsForRelationship == null || connectionsForRelationship.isEmpty()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public Set<Connection> getConnections() {
+ readLock.lock();
+ try {
+ return Collections.unmodifiableSet(outgoingConnections);
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public Set<Connection> getConnections(final Relationship relationship) {
+ readLock.lock();
+ try {
+ if (relationship.equals(PORT_RELATIONSHIP)) {
+ return Collections.unmodifiableSet(outgoingConnections);
+ }
+
+ throw new IllegalArgumentException("No relationship with name " + relationship.getName() + " exists for Local Ports");
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public Position getPosition() {
+ return position.get();
+ }
+
+ @Override
+ public void setPosition(final Position position) {
+ this.position.set(position);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("id", getIdentifier()).toString();
+ }
+
+ @Override
+ public List<Connection> getIncomingConnections() {
+ readLock.lock();
+ try {
+ return Collections.unmodifiableList(incomingConnections);
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public abstract boolean isValid();
+
+ @Override
+ public boolean isAutoTerminated(final Relationship relationship) {
+ return false;
+ }
+
+ @Override
+ public boolean isLossTolerant() {
+ return lossTolerant.get();
+ }
+
+ @Override
+ public void setLossTolerant(boolean lossTolerant) {
+ this.lossTolerant.set(lossTolerant);
+ }
+
+ @Override
+ public void setMaxConcurrentTasks(final int taskCount) {
+ if (taskCount < 1) {
+ throw new IllegalArgumentException();
+ }
+ concurrentTaskCount.set(taskCount);
+ }
+
+ @Override
+ public int getMaxConcurrentTasks() {
+ return concurrentTaskCount.get();
+ }
+
+ @Override
+ public void shutdown() {
+ scheduledState.set(ScheduledState.STOPPED);
+ }
+
+ @Override
+ public void onSchedulingStart() {
+ scheduledState.set(ScheduledState.RUNNING);
+ }
+
+ public void disable() {
+ final boolean updated = scheduledState.compareAndSet(ScheduledState.STOPPED, ScheduledState.DISABLED);
+ if (!updated) {
+ throw new IllegalStateException("Port cannot be disabled because it is not stopped");
+ }
+ }
+
+ public void enable() {
+ final boolean updated = scheduledState.compareAndSet(ScheduledState.DISABLED, ScheduledState.STOPPED);
+ if (!updated) {
+ throw new IllegalStateException("Port cannot be enabled because it is not disabled");
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return getScheduledState().equals(ScheduledState.RUNNING) || processScheduler.getActiveThreadCount(this) > 0;
+ }
+
+ @Override
+ public ScheduledState getScheduledState() {
+ return scheduledState.get();
+ }
+
+ @Override
+ public ConnectableType getConnectableType() {
+ return type;
+ }
+
+ @Override
+ public void setYieldPeriod(final String yieldPeriod) {
+ final long yieldMillis = FormatUtils.getTimeDuration(requireNonNull(yieldPeriod), TimeUnit.MILLISECONDS);
+ if (yieldMillis < 0) {
+ throw new IllegalArgumentException("Yield duration must be positive");
+ }
+ this.yieldPeriod.set(yieldPeriod);
+ }
+
+ @Override
+ public void setScheduldingPeriod(final String schedulingPeriod) {
+ final long schedulingNanos = FormatUtils.getTimeDuration(requireNonNull(schedulingPeriod), TimeUnit.NANOSECONDS);
+ if (schedulingNanos < 0) {
+ throw new IllegalArgumentException("Scheduling Period must be positive");
+ }
+
+ this.schedulingPeriod.set(schedulingPeriod);
+ this.schedulingNanos.set(Math.max(MINIMUM_SCHEDULING_NANOS, schedulingNanos));
+ }
+
+ @Override
+ public long getPenalizationPeriod(final TimeUnit timeUnit) {
+ return FormatUtils.getTimeDuration(getPenalizationPeriod(), timeUnit == null ? DEFAULT_TIME_UNIT : timeUnit);
+ }
+
+ @Override
+ public String getPenalizationPeriod() {
+ return penalizationPeriod.get();
+ }
+
+ @Override
+ public void yield() {
+ final long yieldMillis = getYieldPeriod(TimeUnit.MILLISECONDS);
+ yield(yieldMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void yield(final long yieldDuration, final TimeUnit timeUnit) {
+ final long yieldMillis = timeUnit.toMillis(yieldDuration);
+ yieldExpiration.set(Math.max(yieldExpiration.get(), System.currentTimeMillis() + yieldMillis));
+ }
+
+ @Override
+ public long getYieldExpiration() {
+ return yieldExpiration.get();
+ }
+
+ @Override
+ public long getSchedulingPeriod(final TimeUnit timeUnit) {
+ return timeUnit.convert(schedulingNanos.get(), TimeUnit.NANOSECONDS);
+ }
+
+ @Override
+ public String getSchedulingPeriod() {
+ return schedulingPeriod.get();
+ }
+
+ @Override
+ public void setPenalizationPeriod(final String penalizationPeriod) {
+ this.penalizationPeriod.set(penalizationPeriod);
+ }
+
+ @Override
+ public String getYieldPeriod() {
+ return yieldPeriod.get();
+ }
+
+ @Override
+ public long getYieldPeriod(final TimeUnit timeUnit) {
+ return FormatUtils.getTimeDuration(getYieldPeriod(), timeUnit == null ? DEFAULT_TIME_UNIT : timeUnit);
+ }
+
+ @Override
+ public void verifyCanDelete() throws IllegalStateException {
+ verifyCanDelete(false);
+ }
+
+ @Override
+ public void verifyCanDelete(final boolean ignoreConnections) {
+ readLock.lock();
+ try {
+ if (isRunning()) {
+ throw new IllegalStateException(this.getIdentifier() + " is running");
+ }
+
+ if (!ignoreConnections) {
+ for (final Connection connection : outgoingConnections) {
+ connection.verifyCanDelete();
+ }
+
+ for (final Connection connection : incomingConnections) {
+ if (connection.getSource().equals(this)) {
+ connection.verifyCanDelete();
+ } else {
+ throw new IllegalStateException(this.getIdentifier() + " is the destination of another component");
+ }
+ }
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void verifyCanStart() {
+ readLock.lock();
+ try {
+ switch (scheduledState.get()) {
+ case DISABLED:
+ throw new IllegalStateException(this.getIdentifier() + " cannot be started because it is disabled");
+ case RUNNING:
+ throw new IllegalStateException(this.getIdentifier() + " cannot be started because it is already running");
+ case STOPPED:
+ break;
+ }
+ verifyNoActiveThreads();
+
+ final Collection<ValidationResult> validationResults = getValidationErrors();
+ if (!validationResults.isEmpty()) {
+ throw new IllegalStateException(this.getIdentifier() + " is not in a valid state: " + validationResults.iterator().next().getExplanation());
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void verifyCanStop() {
+ if (getScheduledState() != ScheduledState.RUNNING) {
+ throw new IllegalStateException(this.getIdentifier() + " is not scheduled to run");
+ }
+ }
+
+ @Override
+ public void verifyCanUpdate() {
+ readLock.lock();
+ try {
+ if (isRunning()) {
+ throw new IllegalStateException(this.getIdentifier() + " is not stopped");
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void verifyCanEnable() {
+ readLock.lock();
+ try {
+ if (getScheduledState() != ScheduledState.DISABLED) {
+ throw new IllegalStateException(this.getIdentifier() + " is not disabled");
+ }
+
+ verifyNoActiveThreads();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void verifyCanDisable() {
+ readLock.lock();
+ try {
+ if (getScheduledState() != ScheduledState.STOPPED) {
+ throw new IllegalStateException(this.getIdentifier() + " is not stopped");
+ }
+ verifyNoActiveThreads();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ private void verifyNoActiveThreads() throws IllegalStateException {
+ final int threadCount = processScheduler.getActiveThreadCount(this);
+ if (threadCount > 0) {
+ throw new IllegalStateException(this.getIdentifier() + " has " + threadCount + " threads still active");
+ }
+ }
+
+ @Override
+ public void verifyCanClearState() {
+ }
+
+ @Override
+ public Optional<String> getVersionedComponentId() {
+ return Optional.ofNullable(versionedComponentId.get());
+ }
+
+ @Override
+ public void setVersionedComponentId(final String versionedComponentId) {
+ boolean updated = false;
+ while (!updated) {
+ final String currentId = this.versionedComponentId.get();
+
+ if (currentId == null) {
+ updated = this.versionedComponentId.compareAndSet(null, versionedComponentId);
+ } else if (currentId.equals(versionedComponentId)) {
+ return;
+ } else if (versionedComponentId == null) {
+ updated = this.versionedComponentId.compareAndSet(currentId, null);
+ } else {
+ throw new IllegalStateException(this + " is already under version control");
+ }
+ }
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEAutoLoader.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEAutoLoader.java
new file mode 100644
index 0000000..ec15ba6
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEAutoLoader.java
@@ -0,0 +1,105 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.Executors;
+
+/**
+ * Uses the Java executor service scheduler to continuously load new DCAE jars
+ */
+public class DCAEAutoLoader {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DCAEAutoLoader.class);
+
+ private static final long POLL_INTERVAL_MS = 5000;
+
+ /**
+ * Runnable task that grabs list of remotely stored jars, identifies ones that haven't
+ * been processed, builds Nifi bundles for those unprocessed ones and loads them into
+ * the global extension manager.
+ */
+ private static class LoaderTask implements Runnable {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoaderTask.class);
+
+ private final URI indexJsonDcaeJars;
+ private final ExtensionDiscoveringManager extensionManager;
+ private final Set<URL> processed = new LinkedHashSet();
+
+ private LoaderTask(URI indexJsonDcaeJars, ExtensionDiscoveringManager extensionManager) {
+ this.indexJsonDcaeJars = indexJsonDcaeJars;
+ this.extensionManager = extensionManager;
+ }
+
+ @Override
+ public void run() {
+ try {
+ List<URL> toProcess = DCAEClassLoaders.getDCAEJarsURLs(this.indexJsonDcaeJars);
+ toProcess.removeAll(processed);
+
+ if (!toProcess.isEmpty()) {
+ Set<Bundle> bundles = DCAEClassLoaders.createDCAEBundles(toProcess);
+ this.extensionManager.discoverExtensions(bundles);
+ processed.addAll(toProcess);
+
+ LOGGER.info(String.format("#Added DCAE bundles: %d, #Total DCAE bundles: %d ",
+ bundles.size(), processed.size()));
+ }
+ } catch (final Exception e) {
+ LOGGER.error("Error loading DCAE jars due to: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+ private ScheduledFuture taskFuture;
+
+ public synchronized void start(URI indexJsonDcaeJars, final ExtensionDiscoveringManager extensionManager) {
+ // Restricting to a single thread
+ if (taskFuture != null && !taskFuture.isCancelled()) {
+ return;
+ }
+
+ LOGGER.info("Starting DCAE Auto-Loader: {}", new Object[]{indexJsonDcaeJars});
+
+ LoaderTask task = new LoaderTask(indexJsonDcaeJars, extensionManager);
+ this.taskFuture = executor.scheduleAtFixedRate(task, 0, POLL_INTERVAL_MS, TimeUnit.MILLISECONDS);
+ LOGGER.info("DCAE Auto-Loader started");
+ }
+
+ public synchronized void stop() {
+ if (this.taskFuture != null) {
+ this.taskFuture.cancel(true);
+ LOGGER.info("DCAE Auto-Loader stopped");
+ }
+ }
+
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEClassLoaders.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEClassLoaders.java
new file mode 100644
index 0000000..a4dbe77
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/nar/DCAEClassLoaders.java
@@ -0,0 +1,127 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.jar.Attributes;
+
+
+/**
+ * Class responsible for loading JARs for DCAEProcessors into Nifi
+ */
+public class DCAEClassLoaders {
+
+ public static class DCAEClassLoadersError extends RuntimeException {
+ public DCAEClassLoadersError(Throwable e) {
+ super("Error while using DCAEClassLoaders", e);
+ }
+ }
+
+ /**
+ * Given a URL to a index.json file, fetches the file and generates a list of
+ * URLs for DCAE jars that has Processors packaged.
+ *
+ * @param indexDCAEJars
+ * @return
+ */
+ public static List<URL> getDCAEJarsURLs(URI indexDCAEJars) {
+ JsonFactory jf = new JsonFactory();
+ ObjectMapper om = new ObjectMapper();
+
+ try {
+ List<Object> urls = om.readValue(jf.createParser(indexDCAEJars.toURL()), List.class);
+
+ return urls.stream().map(u -> {
+ try {
+ Map<String, Object> foo = (Map<String, Object>) u;
+ String name = (String) foo.get("name");
+ String url = String.format("%s/%s", indexDCAEJars.toString(), name);
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ // Hopefully you never come here...
+ return null;
+ }
+ }).collect(Collectors.toList());
+ } catch (Exception e) {
+ throw new RuntimeException("Error while getting jar URIs", e);
+ }
+ }
+
+ private static BundleDetails createBundleDetails(URLClassLoader classLoader) {
+ try {
+ URL url = classLoader.findResource("META-INF/MANIFEST.MF");
+ Manifest manifest = new Manifest(url.openStream());
+
+ final Attributes attributes = manifest.getMainAttributes();
+
+ final BundleDetails.Builder builder = new BundleDetails.Builder();
+ // NOTE: Working directory cannot be null so set it to some bogus dir
+ // because we aren't really using this. Or maybe should create our own
+ // working directory
+ builder.workingDir(new File("/tmp"));
+
+ final String group = attributes.getValue("Group");
+ final String id = attributes.getValue("Id");
+ final String version = attributes.getValue("Version");
+ builder.coordinate(new BundleCoordinate(group, id, version));
+
+ return builder.build();
+ } catch (IOException e) {
+ throw new DCAEClassLoadersError(e);
+ }
+ }
+
+ /**
+ * From a list of URLs to remote JARs where the JARs contain DCAEProcessor classes,
+ * create a bundle for each JAR. You will never get a partial list of bundles.
+ *
+ * @param jarURLs
+ * @return
+ */
+ public static Set<Bundle> createDCAEBundles(List<URL> jarURLs) {
+ Set<Bundle> bundles = new HashSet<>();
+
+ for (URL jarURL : jarURLs) {
+ URLClassLoader classLoader = new URLClassLoader(new URL[] {jarURL});
+ Bundle bundle = new Bundle(createBundleDetails(classLoader), classLoader);
+ bundles.add(bundle);
+ }
+
+ return bundles;
+ }
+
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/util/NiFiProperties.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/util/NiFiProperties.java
new file mode 100644
index 0000000..3b341ec
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -0,0 +1,1551 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * The NiFiProperties class holds all properties which are needed for various
+ * values to be available at runtime. It is strongly tied to the startup
+ * properties needed and is often refer to as the 'nifi.properties' file. The
+ * properties contains keys and values. Great care should be taken in leveraging
+ * this class or passing it along. Its use should be refactored and minimized
+ * over time.
+ */
+public abstract class NiFiProperties {
+
+ // core properties
+ public static final String PROPERTIES_FILE_PATH = "nifi.properties.file.path";
+ public static final String FLOW_CONFIGURATION_FILE = "nifi.flow.configuration.file";
+ public static final String FLOW_CONFIGURATION_ARCHIVE_ENABLED = "nifi.flow.configuration.archive.enabled";
+ public static final String FLOW_CONFIGURATION_ARCHIVE_DIR = "nifi.flow.configuration.archive.dir";
+ public static final String FLOW_CONFIGURATION_ARCHIVE_MAX_TIME = "nifi.flow.configuration.archive.max.time";
+ public static final String FLOW_CONFIGURATION_ARCHIVE_MAX_STORAGE = "nifi.flow.configuration.archive.max.storage";
+ public static final String FLOW_CONFIGURATION_ARCHIVE_MAX_COUNT = "nifi.flow.configuration.archive.max.count";
+ public static final String AUTHORIZER_CONFIGURATION_FILE = "nifi.authorizer.configuration.file";
+ public static final String LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "nifi.login.identity.provider.configuration.file";
+ public static final String REPOSITORY_DATABASE_DIRECTORY = "nifi.database.directory";
+ public static final String RESTORE_DIRECTORY = "nifi.restore.directory";
+ public static final String WRITE_DELAY_INTERVAL = "nifi.flowservice.writedelay.interval";
+ public static final String AUTO_RESUME_STATE = "nifi.flowcontroller.autoResumeState";
+ public static final String FLOW_CONTROLLER_GRACEFUL_SHUTDOWN_PERIOD = "nifi.flowcontroller.graceful.shutdown.period";
+ public static final String NAR_LIBRARY_DIRECTORY = "nifi.nar.library.directory";
+ public static final String NAR_LIBRARY_DIRECTORY_PREFIX = "nifi.nar.library.directory.";
+ public static final String NAR_LIBRARY_AUTOLOAD_DIRECTORY = "nifi.nar.library.autoload.directory";
+ public static final String NAR_WORKING_DIRECTORY = "nifi.nar.working.directory";
+ public static final String COMPONENT_DOCS_DIRECTORY = "nifi.documentation.working.directory";
+ public static final String SENSITIVE_PROPS_KEY = "nifi.sensitive.props.key";
+ public static final String SENSITIVE_PROPS_ALGORITHM = "nifi.sensitive.props.algorithm";
+ public static final String SENSITIVE_PROPS_PROVIDER = "nifi.sensitive.props.provider";
+ public static final String H2_URL_APPEND = "nifi.h2.url.append";
+ public static final String REMOTE_INPUT_HOST = "nifi.remote.input.host";
+ public static final String REMOTE_INPUT_PORT = "nifi.remote.input.socket.port";
+ public static final String SITE_TO_SITE_SECURE = "nifi.remote.input.secure";
+ public static final String SITE_TO_SITE_HTTP_ENABLED = "nifi.remote.input.http.enabled";
+ public static final String SITE_TO_SITE_HTTP_TRANSACTION_TTL = "nifi.remote.input.http.transaction.ttl";
+ public static final String REMOTE_CONTENTS_CACHE_EXPIRATION = "nifi.remote.contents.cache.expiration";
+ public static final String TEMPLATE_DIRECTORY = "nifi.templates.directory";
+ public static final String ADMINISTRATIVE_YIELD_DURATION = "nifi.administrative.yield.duration";
+ public static final String PERSISTENT_STATE_DIRECTORY = "nifi.persistent.state.directory";
+ public static final String BORED_YIELD_DURATION = "nifi.bored.yield.duration";
+ public static final String PROCESSOR_SCHEDULING_TIMEOUT = "nifi.processor.scheduling.timeout";
+ public static final String BACKPRESSURE_COUNT = "nifi.queue.backpressure.count";
+ public static final String BACKPRESSURE_SIZE = "nifi.queue.backpressure.size";
+
+ // DCAE related config
+ public static final String DCAE_JARS_INDEX_URL = "nifi.dcae.jars.index.url";
+
+ // content repository properties
+ public static final String REPOSITORY_CONTENT_PREFIX = "nifi.content.repository.directory.";
+ public static final String CONTENT_REPOSITORY_IMPLEMENTATION = "nifi.content.repository.implementation";
+ public static final String MAX_APPENDABLE_CLAIM_SIZE = "nifi.content.claim.max.appendable.size";
+ public static final String MAX_FLOWFILES_PER_CLAIM = "nifi.content.claim.max.flow.files";
+ public static final String CONTENT_ARCHIVE_MAX_RETENTION_PERIOD = "nifi.content.repository.archive.max.retention.period";
+ public static final String CONTENT_ARCHIVE_MAX_USAGE_PERCENTAGE = "nifi.content.repository.archive.max.usage.percentage";
+ public static final String CONTENT_ARCHIVE_BACK_PRESSURE_PERCENTAGE = "nifi.content.repository.archive.backpressure.percentage";
+ public static final String CONTENT_ARCHIVE_ENABLED = "nifi.content.repository.archive.enabled";
+ public static final String CONTENT_ARCHIVE_CLEANUP_FREQUENCY = "nifi.content.repository.archive.cleanup.frequency";
+ public static final String CONTENT_VIEWER_URL = "nifi.content.viewer.url";
+
+ // flowfile repository properties
+ public static final String FLOWFILE_REPOSITORY_IMPLEMENTATION = "nifi.flowfile.repository.implementation";
+ public static final String FLOWFILE_REPOSITORY_ALWAYS_SYNC = "nifi.flowfile.repository.always.sync";
+ public static final String FLOWFILE_REPOSITORY_DIRECTORY = "nifi.flowfile.repository.directory";
+ public static final String FLOWFILE_REPOSITORY_PARTITIONS = "nifi.flowfile.repository.partitions";
+ public static final String FLOWFILE_REPOSITORY_CHECKPOINT_INTERVAL = "nifi.flowfile.repository.checkpoint.interval";
+ public static final String FLOWFILE_SWAP_MANAGER_IMPLEMENTATION = "nifi.swap.manager.implementation";
+ public static final String QUEUE_SWAP_THRESHOLD = "nifi.queue.swap.threshold";
+ public static final String SWAP_IN_THREADS = "nifi.swap.in.threads";
+ public static final String SWAP_IN_PERIOD = "nifi.swap.in.period";
+ public static final String SWAP_OUT_THREADS = "nifi.swap.out.threads";
+ public static final String SWAP_OUT_PERIOD = "nifi.swap.out.period";
+
+ // provenance properties
+ public static final String PROVENANCE_REPO_IMPLEMENTATION_CLASS = "nifi.provenance.repository.implementation";
+ public static final String PROVENANCE_REPO_DIRECTORY_PREFIX = "nifi.provenance.repository.directory.";
+ public static final String PROVENANCE_MAX_STORAGE_TIME = "nifi.provenance.repository.max.storage.time";
+ public static final String PROVENANCE_MAX_STORAGE_SIZE = "nifi.provenance.repository.max.storage.size";
+ public static final String PROVENANCE_ROLLOVER_TIME = "nifi.provenance.repository.rollover.time";
+ public static final String PROVENANCE_ROLLOVER_SIZE = "nifi.provenance.repository.rollover.size";
+ public static final String PROVENANCE_QUERY_THREAD_POOL_SIZE = "nifi.provenance.repository.query.threads";
+ public static final String PROVENANCE_INDEX_THREAD_POOL_SIZE = "nifi.provenance.repository.index.threads";
+ public static final String PROVENANCE_COMPRESS_ON_ROLLOVER = "nifi.provenance.repository.compress.on.rollover";
+ public static final String PROVENANCE_INDEXED_FIELDS = "nifi.provenance.repository.indexed.fields";
+ public static final String PROVENANCE_INDEXED_ATTRIBUTES = "nifi.provenance.repository.indexed.attributes";
+ public static final String PROVENANCE_INDEX_SHARD_SIZE = "nifi.provenance.repository.index.shard.size";
+ public static final String PROVENANCE_JOURNAL_COUNT = "nifi.provenance.repository.journal.count";
+ public static final String PROVENANCE_REPO_ENCRYPTION_KEY = "nifi.provenance.repository.encryption.key";
+ public static final String PROVENANCE_REPO_ENCRYPTION_KEY_ID = "nifi.provenance.repository.encryption.key.id";
+ public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.provenance.repository.encryption.key.provider.implementation";
+ public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.provenance.repository.encryption.key.provider.location";
+ public static final String PROVENANCE_REPO_DEBUG_FREQUENCY = "nifi.provenance.repository.debug.frequency";
+
+ // component status repository properties
+ public static final String COMPONENT_STATUS_REPOSITORY_IMPLEMENTATION = "nifi.components.status.repository.implementation";
+ public static final String COMPONENT_STATUS_SNAPSHOT_FREQUENCY = "nifi.components.status.snapshot.frequency";
+
+ // security properties
+ public static final String SECURITY_KEYSTORE = "nifi.security.keystore";
+ public static final String SECURITY_KEYSTORE_TYPE = "nifi.security.keystoreType";
+ public static final String SECURITY_KEYSTORE_PASSWD = "nifi.security.keystorePasswd";
+ public static final String SECURITY_KEY_PASSWD = "nifi.security.keyPasswd";
+ public static final String SECURITY_TRUSTSTORE = "nifi.security.truststore";
+ public static final String SECURITY_TRUSTSTORE_TYPE = "nifi.security.truststoreType";
+ public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd";
+ public static final String SECURITY_USER_AUTHORIZER = "nifi.security.user.authorizer";
+ public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = "nifi.security.user.login.identity.provider";
+ public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url";
+ public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
+ public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern.";
+ public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value.";
+ public static final String SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX = "nifi.security.identity.mapping.transform.";
+ public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern.";
+ public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value.";
+ public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform.";
+
+ // oidc
+ public static final String SECURITY_USER_OIDC_DISCOVERY_URL = "nifi.security.user.oidc.discovery.url";
+ public static final String SECURITY_USER_OIDC_CONNECT_TIMEOUT = "nifi.security.user.oidc.connect.timeout";
+ public static final String SECURITY_USER_OIDC_READ_TIMEOUT = "nifi.security.user.oidc.read.timeout";
+ public static final String SECURITY_USER_OIDC_CLIENT_ID = "nifi.security.user.oidc.client.id";
+ public static final String SECURITY_USER_OIDC_CLIENT_SECRET = "nifi.security.user.oidc.client.secret";
+ public static final String SECURITY_USER_OIDC_PREFERRED_JWSALGORITHM = "nifi.security.user.oidc.preferred.jwsalgorithm";
+
+ // apache knox
+ public static final String SECURITY_USER_KNOX_URL = "nifi.security.user.knox.url";
+ public static final String SECURITY_USER_KNOX_PUBLIC_KEY = "nifi.security.user.knox.publicKey";
+ public static final String SECURITY_USER_KNOX_COOKIE_NAME = "nifi.security.user.knox.cookieName";
+ public static final String SECURITY_USER_KNOX_AUDIENCES = "nifi.security.user.knox.audiences";
+
+ // web properties
+ public static final String WEB_WAR_DIR = "nifi.web.war.directory";
+ public static final String WEB_HTTP_PORT = "nifi.web.http.port";
+ public static final String WEB_HTTP_PORT_FORWARDING = "nifi.web.http.port.forwarding";
+ public static final String WEB_HTTP_HOST = "nifi.web.http.host";
+ public static final String WEB_HTTP_NETWORK_INTERFACE_PREFIX = "nifi.web.http.network.interface.";
+ public static final String WEB_HTTPS_PORT = "nifi.web.https.port";
+ public static final String WEB_HTTPS_PORT_FORWARDING = "nifi.web.https.port.forwarding";
+ public static final String WEB_HTTPS_HOST = "nifi.web.https.host";
+ public static final String WEB_HTTPS_NETWORK_INTERFACE_PREFIX = "nifi.web.https.network.interface.";
+ public static final String WEB_WORKING_DIR = "nifi.web.jetty.working.directory";
+ public static final String WEB_THREADS = "nifi.web.jetty.threads";
+ public static final String WEB_MAX_HEADER_SIZE = "nifi.web.max.header.size";
+ public static final String WEB_PROXY_CONTEXT_PATH = "nifi.web.proxy.context.path";
+ public static final String WEB_PROXY_HOST = "nifi.web.proxy.host";
+
+ // ui properties
+ public static final String UI_BANNER_TEXT = "nifi.ui.banner.text";
+ public static final String UI_AUTO_REFRESH_INTERVAL = "nifi.ui.autorefresh.interval";
+ public static final String UI_DCAE_DISTRIBUTOR_API_URL="nifi.ui.dcae.distibutor.api.url";
+
+ // cluster common properties
+ public static final String CLUSTER_PROTOCOL_HEARTBEAT_INTERVAL = "nifi.cluster.protocol.heartbeat.interval";
+ public static final String CLUSTER_PROTOCOL_IS_SECURE = "nifi.cluster.protocol.is.secure";
+
+ // cluster node properties
+ public static final String CLUSTER_IS_NODE = "nifi.cluster.is.node";
+ public static final String CLUSTER_NODE_ADDRESS = "nifi.cluster.node.address";
+ public static final String CLUSTER_NODE_PROTOCOL_PORT = "nifi.cluster.node.protocol.port";
+ public static final String CLUSTER_NODE_PROTOCOL_THREADS = "nifi.cluster.node.protocol.threads";
+ public static final String CLUSTER_NODE_PROTOCOL_MAX_THREADS = "nifi.cluster.node.protocol.max.threads";
+ public static final String CLUSTER_NODE_CONNECTION_TIMEOUT = "nifi.cluster.node.connection.timeout";
+ public static final String CLUSTER_NODE_READ_TIMEOUT = "nifi.cluster.node.read.timeout";
+ public static final String CLUSTER_NODE_MAX_CONCURRENT_REQUESTS = "nifi.cluster.node.max.concurrent.requests";
+ public static final String CLUSTER_FIREWALL_FILE = "nifi.cluster.firewall.file";
+ public static final String FLOW_ELECTION_MAX_WAIT_TIME = "nifi.cluster.flow.election.max.wait.time";
+ public static final String FLOW_ELECTION_MAX_CANDIDATES = "nifi.cluster.flow.election.max.candidates";
+
+ // cluster load balance properties
+ public static final String LOAD_BALANCE_ADDRESS = "nifi.cluster.load.balance.address";
+ public static final String LOAD_BALANCE_PORT = "nifi.cluster.load.balance.port";
+ public static final String LOAD_BALANCE_CONNECTIONS_PER_NODE = "nifi.cluster.load.balance.connections.per.node";
+ public static final String LOAD_BALANCE_MAX_THREAD_COUNT = "nifi.cluster.load.balance.max.thread.count";
+ public static final String LOAD_BALANCE_COMMS_TIMEOUT = "nifi.cluster.load.balance.comms.timeout";
+
+ // zookeeper properties
+ public static final String ZOOKEEPER_CONNECT_STRING = "nifi.zookeeper.connect.string";
+ public static final String ZOOKEEPER_CONNECT_TIMEOUT = "nifi.zookeeper.connect.timeout";
+ public static final String ZOOKEEPER_SESSION_TIMEOUT = "nifi.zookeeper.session.timeout";
+ public static final String ZOOKEEPER_ROOT_NODE = "nifi.zookeeper.root.node";
+ public static final String ZOOKEEPER_AUTH_TYPE = "nifi.zookeeper.auth.type";
+ public static final String ZOOKEEPER_KERBEROS_REMOVE_HOST_FROM_PRINCIPAL = "nifi.zookeeper.kerberos.removeHostFromPrincipal";
+ public static final String ZOOKEEPER_KERBEROS_REMOVE_REALM_FROM_PRINCIPAL = "nifi.zookeeper.kerberos.removeRealmFromPrincipal";
+
+ // kerberos properties
+ public static final String KERBEROS_KRB5_FILE = "nifi.kerberos.krb5.file";
+ public static final String KERBEROS_SERVICE_PRINCIPAL = "nifi.kerberos.service.principal";
+ public static final String KERBEROS_SERVICE_KEYTAB_LOCATION = "nifi.kerberos.service.keytab.location";
+ public static final String KERBEROS_SPNEGO_PRINCIPAL = "nifi.kerberos.spnego.principal";
+ public static final String KERBEROS_SPNEGO_KEYTAB_LOCATION = "nifi.kerberos.spnego.keytab.location";
+ public static final String KERBEROS_AUTHENTICATION_EXPIRATION = "nifi.kerberos.spnego.authentication.expiration";
+
+ // state management
+ public static final String STATE_MANAGEMENT_CONFIG_FILE = "nifi.state.management.configuration.file";
+ public static final String STATE_MANAGEMENT_LOCAL_PROVIDER_ID = "nifi.state.management.provider.local";
+ public static final String STATE_MANAGEMENT_CLUSTER_PROVIDER_ID = "nifi.state.management.provider.cluster";
+ public static final String STATE_MANAGEMENT_START_EMBEDDED_ZOOKEEPER = "nifi.state.management.embedded.zookeeper.start";
+ public static final String STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES = "nifi.state.management.embedded.zookeeper.properties";
+
+ // expression language properties
+ public static final String VARIABLE_REGISTRY_PROPERTIES = "nifi.variable.registry.properties";
+
+ // defaults
+ public static final Boolean DEFAULT_AUTO_RESUME_STATE = true;
+ public static final String DEFAULT_AUTHORIZER_CONFIGURATION_FILE = "conf/authorizers.xml";
+ public static final String DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "conf/login-identity-providers.xml";
+ public static final Integer DEFAULT_REMOTE_INPUT_PORT = null;
+ public static final Path DEFAULT_TEMPLATE_DIRECTORY = Paths.get("conf", "templates");
+ public static final int DEFAULT_WEB_THREADS = 200;
+ public static final String DEFAULT_WEB_MAX_HEADER_SIZE = "16 KB";
+ public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
+ public static final String DEFAULT_NAR_WORKING_DIR = "./work/nar";
+ public static final String DEFAULT_COMPONENT_DOCS_DIRECTORY = "./work/docs/components";
+ public static final String DEFAULT_NAR_LIBRARY_DIR = "./lib";
+ public static final String DEFAULT_NAR_LIBRARY_AUTOLOAD_DIR = "./extensions";
+ public static final String DEFAULT_FLOWFILE_REPO_PARTITIONS = "256";
+ public static final String DEFAULT_FLOWFILE_CHECKPOINT_INTERVAL = "2 min";
+ public static final int DEFAULT_MAX_FLOWFILES_PER_CLAIM = 100;
+ public static final String DEFAULT_MAX_APPENDABLE_CLAIM_SIZE = "1 MB";
+ public static final int DEFAULT_QUEUE_SWAP_THRESHOLD = 20000;
+ public static final String DEFAULT_SWAP_STORAGE_LOCATION = "./flowfile_repository/swap";
+ public static final String DEFAULT_SWAP_IN_PERIOD = "1 sec";
+ public static final String DEFAULT_SWAP_OUT_PERIOD = "5 sec";
+ public static final int DEFAULT_SWAP_IN_THREADS = 4;
+ public static final int DEFAULT_SWAP_OUT_THREADS = 4;
+ public static final long DEFAULT_BACKPRESSURE_COUNT = 10_000L;
+ public static final String DEFAULT_BACKPRESSURE_SIZE = "1 GB";
+ public static final String DEFAULT_ADMINISTRATIVE_YIELD_DURATION = "30 sec";
+ public static final String DEFAULT_PERSISTENT_STATE_DIRECTORY = "./conf/state";
+ public static final String DEFAULT_COMPONENT_STATUS_SNAPSHOT_FREQUENCY = "5 mins";
+ public static final String DEFAULT_BORED_YIELD_DURATION = "10 millis";
+ public static final String DEFAULT_ZOOKEEPER_CONNECT_TIMEOUT = "3 secs";
+ public static final String DEFAULT_ZOOKEEPER_SESSION_TIMEOUT = "3 secs";
+ public static final String DEFAULT_ZOOKEEPER_ROOT_NODE = "/nifi";
+ public static final String DEFAULT_ZOOKEEPER_AUTH_TYPE = "default";
+ public static final String DEFAULT_ZOOKEEPER_KERBEROS_REMOVE_HOST_FROM_PRINCIPAL = "true";
+ public static final String DEFAULT_ZOOKEEPER_KERBEROS_REMOVE_REALM_FROM_PRINCIPAL = "true";
+ public static final String DEFAULT_SITE_TO_SITE_HTTP_TRANSACTION_TTL = "30 secs";
+ public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_ENABLED = "true";
+ public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_MAX_TIME = "30 days";
+ public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_MAX_STORAGE = "500 MB";
+ public static final String DEFAULT_SECURITY_USER_OIDC_CONNECT_TIMEOUT = "5 secs";
+ public static final String DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT = "5 secs";
+
+ // DCAE related config
+ // REVIEW: Default is to turn off the dcae jar loading until the platform becomes more accessible/stable
+ public static final String DEFAULT_DCAE_JARS_INDEX_URL = "";
+
+ // cluster common defaults
+ public static final String DEFAULT_CLUSTER_PROTOCOL_HEARTBEAT_INTERVAL = "5 sec";
+ public static final String DEFAULT_CLUSTER_PROTOCOL_MULTICAST_SERVICE_BROADCAST_DELAY = "500 ms";
+ public static final int DEFAULT_CLUSTER_PROTOCOL_MULTICAST_SERVICE_LOCATOR_ATTEMPTS = 3;
+ public static final String DEFAULT_CLUSTER_PROTOCOL_MULTICAST_SERVICE_LOCATOR_ATTEMPTS_DELAY = "1 sec";
+ public static final String DEFAULT_CLUSTER_NODE_READ_TIMEOUT = "5 sec";
+ public static final String DEFAULT_CLUSTER_NODE_CONNECTION_TIMEOUT = "5 sec";
+ public static final int DEFAULT_CLUSTER_NODE_MAX_CONCURRENT_REQUESTS = 100;
+
+ // cluster node defaults
+ public static final int DEFAULT_CLUSTER_NODE_PROTOCOL_THREADS = 10;
+ public static final int DEFAULT_CLUSTER_NODE_PROTOCOL_MAX_THREADS = 50;
+ public static final String DEFAULT_REQUEST_REPLICATION_CLAIM_TIMEOUT = "15 secs";
+ public static final String DEFAULT_FLOW_ELECTION_MAX_WAIT_TIME = "5 mins";
+
+ // cluster load balance defaults
+ public static final int DEFAULT_LOAD_BALANCE_PORT = 6342;
+ public static final int DEFAULT_LOAD_BALANCE_CONNECTIONS_PER_NODE = 4;
+ public static final int DEFAULT_LOAD_BALANCE_MAX_THREAD_COUNT = 8;
+ public static final String DEFAULT_LOAD_BALANCE_COMMS_TIMEOUT = "30 sec";
+
+
+ // state management defaults
+ public static final String DEFAULT_STATE_MANAGEMENT_CONFIG_FILE = "conf/state-management.xml";
+
+ // Kerberos defaults
+ public static final String DEFAULT_KERBEROS_AUTHENTICATION_EXPIRATION = "12 hours";
+
+
+ /**
+ * Retrieves the property value for the given property key.
+ *
+ * @param key the key of property value to lookup
+ * @return value of property at given key or null if not found
+ */
+ public abstract String getProperty(String key);
+
+ /**
+ * Retrieves all known property keys.
+ *
+ * @return all known property keys
+ */
+ public abstract Set<String> getPropertyKeys();
+
+ // getters for core properties //
+ public File getFlowConfigurationFile() {
+ try {
+ return new File(getProperty(FLOW_CONFIGURATION_FILE));
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ public File getFlowConfigurationFileDir() {
+ try {
+ return getFlowConfigurationFile().getParentFile();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private Integer getPropertyAsPort(final String propertyName, final Integer defaultValue) {
+ final String port = getProperty(propertyName);
+ if (StringUtils.isEmpty(port)) {
+ return defaultValue;
+ }
+ try {
+ final int val = Integer.parseInt(port);
+ if (val <= 0 || val > 65535) {
+ throw new RuntimeException("Valid port range is 0 - 65535 but got " + val);
+ }
+ return val;
+ } catch (final NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public int getQueueSwapThreshold() {
+ final String thresholdValue = getProperty(QUEUE_SWAP_THRESHOLD);
+ if (thresholdValue == null) {
+ return DEFAULT_QUEUE_SWAP_THRESHOLD;
+ }
+
+ try {
+ return Integer.parseInt(thresholdValue);
+ } catch (final NumberFormatException e) {
+ return DEFAULT_QUEUE_SWAP_THRESHOLD;
+ }
+ }
+
+ public Integer getIntegerProperty(final String propertyName, final Integer defaultValue) {
+ final String value = getProperty(propertyName);
+ if (value == null || value.trim().isEmpty()) {
+ return defaultValue;
+ }
+
+ try {
+ return Integer.parseInt(value.trim());
+ } catch (final Exception e) {
+ return defaultValue;
+ }
+ }
+
+ public int getSwapInThreads() {
+ return getIntegerProperty(SWAP_IN_THREADS, DEFAULT_SWAP_IN_THREADS);
+ }
+
+ public int getSwapOutThreads() {
+ final String value = getProperty(SWAP_OUT_THREADS);
+ if (value == null) {
+ return DEFAULT_SWAP_OUT_THREADS;
+ }
+
+ try {
+ return Integer.parseInt(getProperty(SWAP_OUT_THREADS));
+ } catch (final Exception e) {
+ return DEFAULT_SWAP_OUT_THREADS;
+ }
+ }
+
+ public String getSwapInPeriod() {
+ return getProperty(SWAP_IN_PERIOD, DEFAULT_SWAP_IN_PERIOD);
+ }
+
+ public String getSwapOutPeriod() {
+ return getProperty(SWAP_OUT_PERIOD, DEFAULT_SWAP_OUT_PERIOD);
+ }
+
+ public String getAdministrativeYieldDuration() {
+ return getProperty(ADMINISTRATIVE_YIELD_DURATION, DEFAULT_ADMINISTRATIVE_YIELD_DURATION);
+ }
+
+ /**
+ * The host name that will be given out to clients to connect to the Remote
+ * Input Port.
+ *
+ * @return the remote input host name or null if not configured
+ */
+ public String getRemoteInputHost() {
+ final String value = getProperty(REMOTE_INPUT_HOST);
+ return StringUtils.isBlank(value) ? null : value;
+ }
+
+ /**
+ * The socket port to listen on for a Remote Input Port.
+ *
+ * @return the remote input port for RAW socket communication
+ */
+ public Integer getRemoteInputPort() {
+ return getPropertyAsPort(REMOTE_INPUT_PORT, DEFAULT_REMOTE_INPUT_PORT);
+ }
+
+ /**
+ * @return False if property value is 'false'; True otherwise.
+ */
+ public Boolean isSiteToSiteSecure() {
+ final String secureVal = getProperty(SITE_TO_SITE_SECURE, "true");
+
+ return !"false".equalsIgnoreCase(secureVal);
+
+ }
+
+ /**
+ * @return True if property value is 'true'; False otherwise.
+ */
+ public Boolean isSiteToSiteHttpEnabled() {
+ final String remoteInputHttpEnabled = getProperty(SITE_TO_SITE_HTTP_ENABLED, "false");
+
+ return "true".equalsIgnoreCase(remoteInputHttpEnabled);
+
+ }
+
+ /**
+ * The HTTP or HTTPS Web API port for a Remote Input Port.
+ *
+ * @return the remote input port for HTTP(S) communication, or null if
+ * HTTP(S) Site-to-Site is not enabled
+ */
+ public Integer getRemoteInputHttpPort() {
+ if (!isSiteToSiteHttpEnabled()) {
+ return null;
+ }
+
+ final String propertyKey;
+ if (isSiteToSiteSecure()) {
+ if (StringUtils.isBlank(getProperty(NiFiProperties.WEB_HTTPS_PORT_FORWARDING))) {
+ propertyKey = WEB_HTTPS_PORT;
+ } else {
+ propertyKey = WEB_HTTPS_PORT_FORWARDING;
+ }
+ } else {
+ if (StringUtils.isBlank(getProperty(NiFiProperties.WEB_HTTP_PORT_FORWARDING))) {
+ propertyKey = WEB_HTTP_PORT;
+ } else {
+ propertyKey = WEB_HTTP_PORT_FORWARDING;
+ }
+ }
+
+ final Integer port = getIntegerProperty(propertyKey, null);
+ if (port == null) {
+ throw new RuntimeException("Remote input HTTP" + (isSiteToSiteSecure() ? "S" : "")
+ + " is enabled but " + propertyKey + " is not specified.");
+ }
+ return port;
+ }
+
+ /**
+ * Returns the directory to which Templates are to be persisted
+ *
+ * @return the template directory
+ */
+ public Path getTemplateDirectory() {
+ final String strVal = getProperty(TEMPLATE_DIRECTORY);
+ return (strVal == null) ? DEFAULT_TEMPLATE_DIRECTORY : Paths.get(strVal);
+ }
+
+ /**
+ * Get the flow service write delay.
+ *
+ * @return The write delay
+ */
+ public String getFlowServiceWriteDelay() {
+ return getProperty(WRITE_DELAY_INTERVAL);
+ }
+
+ /**
+ * Returns whether the processors should be started automatically when the
+ * application loads.
+ *
+ * @return Whether to auto start the processors or not
+ */
+ public boolean getAutoResumeState() {
+ final String rawAutoResumeState = getProperty(AUTO_RESUME_STATE,
+ DEFAULT_AUTO_RESUME_STATE.toString());
+ return Boolean.parseBoolean(rawAutoResumeState);
+ }
+
+ /**
+ * Returns the number of partitions that should be used for the FlowFile
+ * Repository
+ *
+ * @return the number of partitions
+ */
+ public int getFlowFileRepositoryPartitions() {
+ final String rawProperty = getProperty(FLOWFILE_REPOSITORY_PARTITIONS,
+ DEFAULT_FLOWFILE_REPO_PARTITIONS);
+ return Integer.parseInt(rawProperty);
+ }
+
+ /**
+ * Returns the number of milliseconds between FlowFileRepository
+ * checkpointing
+ *
+ * @return the number of milliseconds between checkpoint events
+ */
+ public String getFlowFileRepositoryCheckpointInterval() {
+ return getProperty(FLOWFILE_REPOSITORY_CHECKPOINT_INTERVAL,
+ DEFAULT_FLOWFILE_CHECKPOINT_INTERVAL);
+ }
+
+ /**
+ * @return the restore directory or null if not configured
+ */
+ public File getRestoreDirectory() {
+ final String value = getProperty(RESTORE_DIRECTORY);
+ if (StringUtils.isBlank(value)) {
+ return null;
+ } else {
+ return new File(value);
+ }
+ }
+
+ /**
+ * @return the user authorizers file
+ */
+ public File getAuthorizerConfigurationFile() {
+ final String value = getProperty(AUTHORIZER_CONFIGURATION_FILE);
+ if (StringUtils.isBlank(value)) {
+ return new File(DEFAULT_AUTHORIZER_CONFIGURATION_FILE);
+ } else {
+ return new File(value);
+ }
+ }
+
+ /**
+ * @return the user login identity provider file
+ */
+ public File getLoginIdentityProviderConfigurationFile() {
+ final String value = getProperty(LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);
+ if (StringUtils.isBlank(value)) {
+ return new File(DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);
+ } else {
+ return new File(value);
+ }
+ }
+
+ // getters for web properties //
+ public Integer getPort() {
+ Integer port = null;
+ try {
+ port = Integer.parseInt(getProperty(WEB_HTTP_PORT));
+ } catch (NumberFormatException nfe) {
+ }
+ return port;
+ }
+
+ public Integer getSslPort() {
+ Integer sslPort = null;
+ try {
+ sslPort = Integer.parseInt(getProperty(WEB_HTTPS_PORT));
+ } catch (NumberFormatException nfe) {
+ }
+ return sslPort;
+ }
+
+ public boolean isHTTPSConfigured() {
+ return getSslPort() != null;
+ }
+
+ /**
+ * Determines the HTTP/HTTPS port NiFi is configured to bind to. Prefers the HTTPS port. Throws an exception if neither is configured.
+ *
+ * @return the configured port number
+ */
+ public Integer getConfiguredHttpOrHttpsPort() throws RuntimeException {
+ if (getSslPort() != null) {
+ return getSslPort();
+ } else if (getPort() != null) {
+ return getPort();
+ } else {
+ throw new RuntimeException("The HTTP or HTTPS port must be configured");
+ }
+ }
+
+ public String getWebMaxHeaderSize() {
+ return getProperty(WEB_MAX_HEADER_SIZE, DEFAULT_WEB_MAX_HEADER_SIZE);
+ }
+
+ public int getWebThreads() {
+ return getIntegerProperty(WEB_THREADS, DEFAULT_WEB_THREADS);
+ }
+
+ public int getClusterNodeMaxConcurrentRequests() {
+ return getIntegerProperty(CLUSTER_NODE_MAX_CONCURRENT_REQUESTS, DEFAULT_CLUSTER_NODE_MAX_CONCURRENT_REQUESTS);
+ }
+
+ public File getWebWorkingDirectory() {
+ return new File(getProperty(WEB_WORKING_DIR, DEFAULT_WEB_WORKING_DIR));
+ }
+
+ public File getComponentDocumentationWorkingDirectory() {
+ return new File(getProperty(COMPONENT_DOCS_DIRECTORY, DEFAULT_COMPONENT_DOCS_DIRECTORY));
+ }
+
+ public File getNarWorkingDirectory() {
+ return new File(getProperty(NAR_WORKING_DIRECTORY, DEFAULT_NAR_WORKING_DIR));
+ }
+
+ public File getFrameworkWorkingDirectory() {
+ return new File(getNarWorkingDirectory(), "framework");
+ }
+
+ public File getExtensionsWorkingDirectory() {
+ return new File(getNarWorkingDirectory(), "extensions");
+ }
+
+ public List<Path> getNarLibraryDirectories() {
+
+ List<Path> narLibraryPaths = new ArrayList<>();
+
+ // go through each property
+ for (String propertyName : getPropertyKeys()) {
+ // determine if the property is a nar library path
+ if (StringUtils.startsWith(propertyName, NAR_LIBRARY_DIRECTORY_PREFIX)
+ || NAR_LIBRARY_DIRECTORY.equals(propertyName)
+ || NAR_LIBRARY_AUTOLOAD_DIRECTORY.equals(propertyName)) {
+ // attempt to resolve the path specified
+ String narLib = getProperty(propertyName);
+ if (!StringUtils.isBlank(narLib)) {
+ narLibraryPaths.add(Paths.get(narLib));
+ }
+ }
+ }
+
+ if (narLibraryPaths.isEmpty()) {
+ narLibraryPaths.add(Paths.get(DEFAULT_NAR_LIBRARY_DIR));
+ }
+
+ return narLibraryPaths;
+ }
+
+ public File getNarAutoLoadDirectory() {
+ return new File(getProperty(NAR_LIBRARY_AUTOLOAD_DIRECTORY, DEFAULT_NAR_LIBRARY_AUTOLOAD_DIR));
+ }
+
+ /**
+ * Retrieves a URI to the index that contains URLs to all the DCAE jars to be loaded into Nifi.
+ * Refer to the genprocessor project for more info.
+ *
+ * Not setting the underlying configuration parameter "nifi.dcae.jar.index.url" is not
+ * fatal. Nifi will just skip over trying to load DCAE jars.
+ *
+ * @return
+ * @throws URISyntaxException
+ */
+ public URI getDCAEJarIndexURI() throws URISyntaxException {
+ String strUrl = getProperty(DCAE_JARS_INDEX_URL, DEFAULT_DCAE_JARS_INDEX_URL);
+
+ if (strUrl == null || strUrl.isEmpty()) {
+ return null;
+ } else {
+ return new URI(strUrl);
+ }
+ }
+
+ // getters for ui properties //
+
+ /**
+ * Get the banner text.
+ *
+ * @return The banner text
+ */
+ public String getBannerText() {
+ return this.getProperty(UI_BANNER_TEXT, StringUtils.EMPTY);
+ }
+
+
+ /**
+ * @author Renu
+ * @return the IP address where the nifi-app is being hosted
+ */
+ public String getDcaeDistributorApiHostname() {
+ return getProperty(UI_DCAE_DISTRIBUTOR_API_URL);
+ }
+
+ /**
+ * Returns the auto refresh interval in seconds.
+ *
+ * @return the interval over which the properties should auto refresh
+ */
+ public String getAutoRefreshInterval() {
+ return getProperty(UI_AUTO_REFRESH_INTERVAL);
+ }
+
+ // getters for cluster protocol properties //
+ public String getClusterProtocolHeartbeatInterval() {
+ return getProperty(CLUSTER_PROTOCOL_HEARTBEAT_INTERVAL,
+ DEFAULT_CLUSTER_PROTOCOL_HEARTBEAT_INTERVAL);
+ }
+
+ public String getNodeHeartbeatInterval() {
+ return getClusterProtocolHeartbeatInterval();
+ }
+
+ public String getClusterNodeReadTimeout() {
+ return getProperty(CLUSTER_NODE_READ_TIMEOUT, DEFAULT_CLUSTER_NODE_READ_TIMEOUT);
+ }
+
+ public String getClusterNodeConnectionTimeout() {
+ return getProperty(CLUSTER_NODE_CONNECTION_TIMEOUT,
+ DEFAULT_CLUSTER_NODE_CONNECTION_TIMEOUT);
+ }
+
+ public File getPersistentStateDirectory() {
+ final String dirName = getProperty(PERSISTENT_STATE_DIRECTORY,
+ DEFAULT_PERSISTENT_STATE_DIRECTORY);
+ final File file = new File(dirName);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return file;
+ }
+
+ // getters for cluster node properties //
+ public boolean isNode() {
+ return Boolean.parseBoolean(getProperty(CLUSTER_IS_NODE));
+ }
+
+ public InetSocketAddress getClusterNodeProtocolAddress() {
+ try {
+ String socketAddress = getProperty(CLUSTER_NODE_ADDRESS);
+ if (StringUtils.isBlank(socketAddress)) {
+ socketAddress = "localhost";
+ }
+ int socketPort = getClusterNodeProtocolPort();
+ return InetSocketAddress.createUnresolved(socketAddress, socketPort);
+ } catch (Exception ex) {
+ throw new RuntimeException("Invalid node protocol address/port due to: " + ex, ex);
+ }
+ }
+
+ public InetSocketAddress getClusterLoadBalanceAddress() {
+ try {
+ String address = getProperty(LOAD_BALANCE_ADDRESS);
+ if (StringUtils.isBlank(address)) {
+ address = getProperty(CLUSTER_NODE_ADDRESS);
+ }
+ if (StringUtils.isBlank(address)) {
+ address = "localhost";
+ }
+
+ final int port = getIntegerProperty(LOAD_BALANCE_PORT, DEFAULT_LOAD_BALANCE_PORT);
+ return InetSocketAddress.createUnresolved(address, port);
+ } catch (final Exception e) {
+ throw new RuntimeException("Invalid load balance address/port due to: " + e, e);
+ }
+ }
+
+ public Integer getClusterNodeProtocolPort() {
+ try {
+ return Integer.parseInt(getProperty(CLUSTER_NODE_PROTOCOL_PORT));
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ }
+
+ /**
+ * @deprecated Use getClusterNodeProtocolCorePoolSize() and getClusterNodeProtocolMaxPoolSize() instead
+ */
+ @Deprecated()
+ public int getClusterNodeProtocolThreads() {
+ return getClusterNodeProtocolCorePoolSize();
+ }
+
+ public int getClusterNodeProtocolCorePoolSize() {
+ try {
+ return Integer.parseInt(getProperty(CLUSTER_NODE_PROTOCOL_THREADS));
+ } catch (NumberFormatException nfe) {
+ return DEFAULT_CLUSTER_NODE_PROTOCOL_THREADS;
+ }
+ }
+
+ public int getClusterNodeProtocolMaxPoolSize() {
+ try {
+ return Integer.parseInt(getProperty(CLUSTER_NODE_PROTOCOL_MAX_THREADS));
+ } catch (NumberFormatException nfe) {
+ return DEFAULT_CLUSTER_NODE_PROTOCOL_MAX_THREADS;
+ }
+ }
+
+ public boolean isClustered() {
+ return Boolean.parseBoolean(getProperty(CLUSTER_IS_NODE));
+ }
+
+ public File getClusterNodeFirewallFile() {
+ final String firewallFile = getProperty(CLUSTER_FIREWALL_FILE);
+ if (StringUtils.isBlank(firewallFile)) {
+ return null;
+ } else {
+ return new File(firewallFile);
+ }
+ }
+
+ public String getClusterProtocolManagerToNodeApiScheme() {
+ final String isSecureProperty = getProperty(CLUSTER_PROTOCOL_IS_SECURE);
+ if (Boolean.valueOf(isSecureProperty)) {
+ return "https";
+ } else {
+ return "http";
+ }
+ }
+
+ public File getKerberosConfigurationFile() {
+ final String krb5File = getProperty(KERBEROS_KRB5_FILE);
+ if (krb5File != null && krb5File.trim().length() > 0) {
+ return new File(krb5File.trim());
+ } else {
+ return null;
+ }
+ }
+
+ public String getKerberosServicePrincipal() {
+ final String servicePrincipal = getProperty(KERBEROS_SERVICE_PRINCIPAL);
+ if (!StringUtils.isBlank(servicePrincipal)) {
+ return servicePrincipal.trim();
+ } else {
+ return null;
+ }
+ }
+
+ public String getKerberosServiceKeytabLocation() {
+ final String keytabLocation = getProperty(KERBEROS_SERVICE_KEYTAB_LOCATION);
+ if (!StringUtils.isBlank(keytabLocation)) {
+ return keytabLocation.trim();
+ } else {
+ return null;
+ }
+ }
+
+ public String getKerberosSpnegoPrincipal() {
+ final String spengoPrincipal = getProperty(KERBEROS_SPNEGO_PRINCIPAL);
+ if (!StringUtils.isBlank(spengoPrincipal)) {
+ return spengoPrincipal.trim();
+ } else {
+ return null;
+ }
+ }
+
+ public String getKerberosSpnegoKeytabLocation() {
+ final String keytabLocation = getProperty(KERBEROS_SPNEGO_KEYTAB_LOCATION);
+ if (!StringUtils.isBlank(keytabLocation)) {
+ return keytabLocation.trim();
+ } else {
+ return null;
+ }
+ }
+
+ public String getKerberosAuthenticationExpiration() {
+ final String authenticationExpirationString = getProperty(KERBEROS_AUTHENTICATION_EXPIRATION, DEFAULT_KERBEROS_AUTHENTICATION_EXPIRATION);
+ if (!StringUtils.isBlank(authenticationExpirationString)) {
+ return authenticationExpirationString.trim();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if the Kerberos service principal and keytab location
+ * properties are populated.
+ *
+ * @return true if Kerberos service support is enabled
+ */
+ public boolean isKerberosSpnegoSupportEnabled() {
+ return !StringUtils.isBlank(getKerberosSpnegoPrincipal()) && !StringUtils.isBlank(getKerberosSpnegoKeytabLocation());
+ }
+
+ /**
+ * Returns true if the login identity provider has been configured.
+ *
+ * @return true if the login identity provider has been configured
+ */
+ public boolean isLoginIdentityProviderEnabled() {
+ return !StringUtils.isBlank(getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER));
+ }
+
+ /**
+ * Returns whether an OpenId Connect (OIDC) URL is set.
+ *
+ * @return whether an OpenId Connection URL is set
+ */
+ public boolean isOidcEnabled() {
+ return !StringUtils.isBlank(getOidcDiscoveryUrl());
+ }
+
+ /**
+ * Returns the OpenId Connect (OIDC) URL. Null otherwise.
+ *
+ * @return OIDC discovery url
+ */
+ public String getOidcDiscoveryUrl() {
+ return getProperty(SECURITY_USER_OIDC_DISCOVERY_URL);
+ }
+
+ /**
+ * Returns the OpenId Connect connect timeout. Non null.
+ *
+ * @return OIDC connect timeout
+ */
+ public String getOidcConnectTimeout() {
+ return getProperty(SECURITY_USER_OIDC_CONNECT_TIMEOUT, DEFAULT_SECURITY_USER_OIDC_CONNECT_TIMEOUT);
+ }
+
+ /**
+ * Returns the OpenId Connect read timeout. Non null.
+ *
+ * @return OIDC read timeout
+ */
+ public String getOidcReadTimeout() {
+ return getProperty(SECURITY_USER_OIDC_READ_TIMEOUT, DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT);
+ }
+
+ /**
+ * Returns the OpenId Connect client id.
+ *
+ * @return OIDC client id
+ */
+ public String getOidcClientId() {
+ return getProperty(SECURITY_USER_OIDC_CLIENT_ID);
+ }
+
+ /**
+ * Returns the OpenId Connect client secret.
+ *
+ * @return OIDC client secret
+ */
+ public String getOidcClientSecret() {
+ return getProperty(SECURITY_USER_OIDC_CLIENT_SECRET);
+ }
+
+ /**
+ * Returns the preferred json web signature algorithm. May be null/blank.
+ *
+ * @return OIDC preferred json web signature algorithm
+ */
+ public String getOidcPreferredJwsAlgorithm() {
+ return getProperty(SECURITY_USER_OIDC_PREFERRED_JWSALGORITHM);
+ }
+
+ /**
+ * Returns whether Knox SSO is enabled.
+ *
+ * @return whether Knox SSO is enabled
+ */
+ public boolean isKnoxSsoEnabled() {
+ return !StringUtils.isBlank(getKnoxUrl());
+ }
+
+ /**
+ * Returns the Knox URL.
+ *
+ * @return Knox URL
+ */
+ public String getKnoxUrl() {
+ return getProperty(SECURITY_USER_KNOX_URL);
+ }
+
+ /**
+ * Gets the configured Knox Audiences.
+ *
+ * @return Knox audiences
+ */
+ public Set<String> getKnoxAudiences() {
+ final String rawAudiences = getProperty(SECURITY_USER_KNOX_AUDIENCES);
+ if (StringUtils.isBlank(rawAudiences)) {
+ return null;
+ } else {
+ final String[] audienceTokens = rawAudiences.split(",");
+ return Stream.of(audienceTokens).map(String::trim).filter(aud -> !StringUtils.isEmpty(aud)).collect(Collectors.toSet());
+ }
+ }
+
+ /**
+ * Returns the path to the Knox public key.
+ *
+ * @return path to the Knox public key
+ */
+ public Path getKnoxPublicKeyPath() {
+ return Paths.get(getProperty(SECURITY_USER_KNOX_PUBLIC_KEY));
+ }
+
+ /**
+ * Returns the name of the Knox cookie.
+ *
+ * @return name of the Knox cookie
+ */
+ public String getKnoxCookieName() {
+ return getProperty(SECURITY_USER_KNOX_COOKIE_NAME);
+ }
+
+ /**
+ * Returns true if client certificates are required for REST API. Determined
+ * if the following conditions are all true:
+ * <p>
+ * - login identity provider is not populated
+ * - Kerberos service support is not enabled
+ * - openid connect is not enabled
+ * - knox sso is not enabled
+ * </p>
+ *
+ * @return true if client certificates are required for access to the REST API
+ */
+ public boolean isClientAuthRequiredForRestApi() {
+ return !isLoginIdentityProviderEnabled() && !isKerberosSpnegoSupportEnabled() && !isOidcEnabled() && !isKnoxSsoEnabled();
+ }
+
+ public InetSocketAddress getNodeApiAddress() {
+
+ final String rawScheme = getClusterProtocolManagerToNodeApiScheme();
+ final String scheme = (rawScheme == null) ? "http" : rawScheme;
+
+ final String host;
+ final Integer port;
+ if ("http".equalsIgnoreCase(scheme)) {
+ // get host
+ if (StringUtils.isBlank(getProperty(WEB_HTTP_HOST))) {
+ host = "localhost";
+ } else {
+ host = getProperty(WEB_HTTP_HOST);
+ }
+ // get port
+ port = getPort();
+
+ if (port == null) {
+ throw new RuntimeException(String.format("The %s must be specified if running in a cluster with %s set to false.", WEB_HTTP_PORT, CLUSTER_PROTOCOL_IS_SECURE));
+ }
+ } else {
+ // get host
+ if (StringUtils.isBlank(getProperty(WEB_HTTPS_HOST))) {
+ host = "localhost";
+ } else {
+ host = getProperty(WEB_HTTPS_HOST);
+ }
+ // get port
+ port = getSslPort();
+
+ if (port == null) {
+ throw new RuntimeException(String.format("The %s must be specified if running in a cluster with %s set to true.", WEB_HTTPS_PORT, CLUSTER_PROTOCOL_IS_SECURE));
+ }
+ }
+
+ return InetSocketAddress.createUnresolved(host, port);
+
+ }
+
+ /**
+ * Returns the database repository path. It simply returns the value
+ * configured. No directories will be created as a result of this operation.
+ *
+ * @return database repository path
+ * @throws InvalidPathException If the configured path is invalid
+ */
+ public Path getDatabaseRepositoryPath() {
+ return Paths.get(getProperty(REPOSITORY_DATABASE_DIRECTORY));
+ }
+
+ /**
+ * Returns the flow file repository path. It simply returns the value
+ * configured. No directories will be created as a result of this operation.
+ *
+ * @return database repository path
+ * @throws InvalidPathException If the configured path is invalid
+ */
+ public Path getFlowFileRepositoryPath() {
+ return Paths.get(getProperty(FLOWFILE_REPOSITORY_DIRECTORY));
+ }
+
+ /**
+ * Returns the content repository paths. This method returns a mapping of
+ * file repository name to file repository paths. It simply returns the
+ * values configured. No directories will be created as a result of this
+ * operation.
+ *
+ * @return file repositories paths
+ * @throws InvalidPathException If any of the configured paths are invalid
+ */
+ public Map<String, Path> getContentRepositoryPaths() {
+ final Map<String, Path> contentRepositoryPaths = new HashMap<>();
+
+ // go through each property
+ for (String propertyName : getPropertyKeys()) {
+ // determine if the property is a file repository path
+ if (StringUtils.startsWith(propertyName, REPOSITORY_CONTENT_PREFIX)) {
+ // get the repository key
+ final String key = StringUtils.substringAfter(propertyName,
+ REPOSITORY_CONTENT_PREFIX);
+
+ // attempt to resolve the path specified
+ contentRepositoryPaths.put(key, Paths.get(getProperty(propertyName)));
+ }
+ }
+ return contentRepositoryPaths;
+ }
+
+ /**
+ * Returns the provenance repository paths. This method returns a mapping of
+ * file repository name to file repository paths. It simply returns the
+ * values configured. No directories will be created as a result of this
+ * operation.
+ *
+ * @return the name and paths of all provenance repository locations
+ */
+ public Map<String, Path> getProvenanceRepositoryPaths() {
+ final Map<String, Path> provenanceRepositoryPaths = new HashMap<>();
+
+ // go through each property
+ for (String propertyName : getPropertyKeys()) {
+ // determine if the property is a file repository path
+ if (StringUtils.startsWith(propertyName, PROVENANCE_REPO_DIRECTORY_PREFIX)) {
+ // get the repository key
+ final String key = StringUtils.substringAfter(propertyName,
+ PROVENANCE_REPO_DIRECTORY_PREFIX);
+
+ // attempt to resolve the path specified
+ provenanceRepositoryPaths.put(key, Paths.get(getProperty(propertyName)));
+ }
+ }
+ return provenanceRepositoryPaths;
+ }
+
+ /**
+ * Returns the number of claims to keep open for writing. Ideally, this will be at
+ * least as large as the number of threads that will be updating the repository simultaneously but we don't want
+ * to get too large because it will hold open up to this many FileOutputStreams.
+ * <p>
+ * Default is {@link #DEFAULT_MAX_FLOWFILES_PER_CLAIM}
+ *
+ * @return the maximum number of flow files per claim
+ */
+ public int getMaxFlowFilesPerClaim() {
+ try {
+ return Integer.parseInt(getProperty(MAX_FLOWFILES_PER_CLAIM));
+ } catch (NumberFormatException nfe) {
+ return DEFAULT_MAX_FLOWFILES_PER_CLAIM;
+ }
+ }
+
+ /**
+ * Returns the maximum size, in bytes, that claims should grow before writing a new file. This means that we won't continually write to one
+ * file that keeps growing but gives us a chance to bunch together many small files.
+ * <p>
+ * Default is {@link #DEFAULT_MAX_APPENDABLE_CLAIM_SIZE}
+ *
+ * @return the maximum appendable claim size
+ */
+ public String getMaxAppendableClaimSize() {
+ return getProperty(MAX_APPENDABLE_CLAIM_SIZE, DEFAULT_MAX_APPENDABLE_CLAIM_SIZE);
+ }
+
+ public String getProperty(final String key, final String defaultValue) {
+ final String value = getProperty(key);
+ return (value == null || value.trim().isEmpty()) ? defaultValue : value;
+ }
+
+ public String getBoredYieldDuration() {
+ return getProperty(BORED_YIELD_DURATION, DEFAULT_BORED_YIELD_DURATION);
+ }
+
+ public File getStateManagementConfigFile() {
+ return new File(getProperty(STATE_MANAGEMENT_CONFIG_FILE, DEFAULT_STATE_MANAGEMENT_CONFIG_FILE));
+ }
+
+ public String getLocalStateProviderId() {
+ return getProperty(STATE_MANAGEMENT_LOCAL_PROVIDER_ID);
+ }
+
+ public String getClusterStateProviderId() {
+ return getProperty(STATE_MANAGEMENT_CLUSTER_PROVIDER_ID);
+ }
+
+ public File getEmbeddedZooKeeperPropertiesFile() {
+ final String filename = getProperty(STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES);
+ return filename == null ? null : new File(filename);
+ }
+
+ public boolean isStartEmbeddedZooKeeper() {
+ return Boolean.parseBoolean(getProperty(STATE_MANAGEMENT_START_EMBEDDED_ZOOKEEPER));
+ }
+
+ public boolean isFlowConfigurationArchiveEnabled() {
+ return Boolean.parseBoolean(getProperty(FLOW_CONFIGURATION_ARCHIVE_ENABLED, DEFAULT_FLOW_CONFIGURATION_ARCHIVE_ENABLED));
+ }
+
+ public String getFlowConfigurationArchiveDir() {
+ return getProperty(FLOW_CONFIGURATION_ARCHIVE_DIR);
+ }
+
+ public String getFlowElectionMaxWaitTime() {
+ return getProperty(FLOW_ELECTION_MAX_WAIT_TIME, DEFAULT_FLOW_ELECTION_MAX_WAIT_TIME);
+ }
+
+ public Integer getFlowElectionMaxCandidates() {
+ return getIntegerProperty(FLOW_ELECTION_MAX_CANDIDATES, null);
+ }
+
+ public String getFlowConfigurationArchiveMaxTime() {
+ return getProperty(FLOW_CONFIGURATION_ARCHIVE_MAX_TIME, null);
+ }
+
+ public String getFlowConfigurationArchiveMaxStorage() {
+ return getProperty(FLOW_CONFIGURATION_ARCHIVE_MAX_STORAGE, null);
+ }
+
+ public Integer getFlowConfigurationArchiveMaxCount() {
+ return getIntegerProperty(FLOW_CONFIGURATION_ARCHIVE_MAX_COUNT, null);
+ }
+
+ public String getVariableRegistryProperties() {
+ return getProperty(VARIABLE_REGISTRY_PROPERTIES);
+ }
+
+ public Path[] getVariableRegistryPropertiesPaths() {
+ final List<Path> vrPropertiesPaths = new ArrayList<>();
+
+ final String vrPropertiesFiles = getVariableRegistryProperties();
+ if (!StringUtils.isEmpty(vrPropertiesFiles)) {
+
+ final List<String> vrPropertiesFileList = Arrays.asList(vrPropertiesFiles.split(","));
+
+ for (String propertiesFile : vrPropertiesFileList) {
+ vrPropertiesPaths.add(Paths.get(propertiesFile));
+ }
+
+ return vrPropertiesPaths.toArray(new Path[vrPropertiesPaths.size()]);
+ } else {
+ return new Path[]{};
+ }
+ }
+
+ /**
+ * Returns the network interface list to use for HTTP. This method returns a mapping of
+ * network interface property names to network interface names.
+ *
+ * @return the property name and network interface name of all HTTP network interfaces
+ */
+ public Map<String, String> getHttpNetworkInterfaces() {
+ final Map<String, String> networkInterfaces = new HashMap<>();
+
+ // go through each property
+ for (String propertyName : getPropertyKeys()) {
+ // determine if the property is a network interface name
+ if (StringUtils.startsWith(propertyName, WEB_HTTP_NETWORK_INTERFACE_PREFIX)) {
+ // get the network interface property key
+ final String key = StringUtils.substringAfter(propertyName,
+ WEB_HTTP_NETWORK_INTERFACE_PREFIX);
+ networkInterfaces.put(key, getProperty(propertyName));
+ }
+ }
+ return networkInterfaces;
+ }
+
+ /**
+ * Returns the network interface list to use for HTTPS. This method returns a mapping of
+ * network interface property names to network interface names.
+ *
+ * @return the property name and network interface name of all HTTPS network interfaces
+ */
+ public Map<String, String> getHttpsNetworkInterfaces() {
+ final Map<String, String> networkInterfaces = new HashMap<>();
+
+ // go through each property
+ for (String propertyName : getPropertyKeys()) {
+ // determine if the property is a network interface name
+ if (StringUtils.startsWith(propertyName, WEB_HTTPS_NETWORK_INTERFACE_PREFIX)) {
+ // get the network interface property key
+ final String key = StringUtils.substringAfter(propertyName,
+ WEB_HTTPS_NETWORK_INTERFACE_PREFIX);
+ networkInterfaces.put(key, getProperty(propertyName));
+ }
+ }
+ return networkInterfaces;
+ }
+
+ public int size() {
+ return getPropertyKeys().size();
+ }
+
+ public String getProvenanceRepoEncryptionKeyId() {
+ return getProperty(PROVENANCE_REPO_ENCRYPTION_KEY_ID);
+ }
+
+ /**
+ * Returns the active provenance repository encryption key if a {@code StaticKeyProvider} is in use.
+ * If no key ID is specified in the properties file, the default
+ * {@code nifi.provenance.repository.encryption.key} value is returned. If a key ID is specified in
+ * {@code nifi.provenance.repository.encryption.key.id}, it will attempt to read from
+ * {@code nifi.provenance.repository.encryption.key.id.XYZ} where {@code XYZ} is the provided key
+ * ID. If that value is empty, it will use the default property
+ * {@code nifi.provenance.repository.encryption.key}.
+ *
+ * @return the provenance repository encryption key in hex form
+ */
+ public String getProvenanceRepoEncryptionKey() {
+ String keyId = getProvenanceRepoEncryptionKeyId();
+ String keyKey = StringUtils.isBlank(keyId) ? PROVENANCE_REPO_ENCRYPTION_KEY : PROVENANCE_REPO_ENCRYPTION_KEY + ".id." + keyId;
+ return getProperty(keyKey, getProperty(PROVENANCE_REPO_ENCRYPTION_KEY));
+ }
+
+ /**
+ * Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
+ * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
+ * {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
+ *
+ * @return a Map of the keys identified by key ID
+ */
+ public Map<String, String> getProvenanceRepoEncryptionKeys() {
+ Map<String, String> keys = new HashMap<>();
+ List<String> keyProperties = getProvenanceRepositoryEncryptionKeyProperties();
+
+ // Retrieve the actual key values and store non-empty values in the map
+ for (String prop : keyProperties) {
+ final String value = getProperty(prop);
+ if (!StringUtils.isBlank(value)) {
+ if (prop.equalsIgnoreCase(PROVENANCE_REPO_ENCRYPTION_KEY)) {
+ prop = getProvenanceRepoEncryptionKeyId();
+ } else {
+ // Extract nifi.provenance.repository.encryption.key.id.key1 -> key1
+ prop = prop.substring(prop.lastIndexOf(".") + 1);
+ }
+ keys.put(prop, value);
+ }
+ }
+ return keys;
+ }
+
+ /**
+ * Returns the whitelisted proxy hostnames (and IP addresses) as a comma-delimited string.
+ * The hosts have been normalized to the form {@code somehost.com}, {@code somehost.com:port}, or {@code 127.0.0.1}.
+ * <p>
+ * Note: Calling {@code NiFiProperties.getProperty(NiFiProperties.WEB_PROXY_HOST)} will not normalize the hosts.
+ *
+ * @return the hostname(s)
+ */
+ public String getWhitelistedHosts() {
+ return StringUtils.join(getWhitelistedHostsAsList(), ",");
+ }
+
+ /**
+ * Returns the whitelisted proxy hostnames (and IP addresses) as a List. The hosts have been normalized to the form {@code somehost.com}, {@code somehost.com:port}, or {@code 127.0.0.1}.
+ *
+ * @return the hostname(s)
+ */
+ public List<String> getWhitelistedHostsAsList() {
+ String rawProperty = getProperty(WEB_PROXY_HOST, "");
+ List<String> hosts = Arrays.asList(rawProperty.split(","));
+ return hosts.stream()
+ .map(this::normalizeHost).filter(host -> !StringUtils.isBlank(host)).collect(Collectors.toList());
+ }
+
+ String normalizeHost(String host) {
+ if (host == null || host.equalsIgnoreCase("")) {
+ return "";
+ } else {
+ return host.trim();
+ }
+ }
+
+ /**
+ * Returns the whitelisted proxy context paths as a comma-delimited string. The paths have been normalized to the form {@code /some/context/path}.
+ * <p>
+ * Note: Calling {@code NiFiProperties.getProperty(NiFiProperties.WEB_PROXY_CONTEXT_PATH)} will not normalize the paths.
+ *
+ * @return the path(s)
+ */
+ public String getWhitelistedContextPaths() {
+ return StringUtils.join(getWhitelistedContextPathsAsList(), ",");
+ }
+
+ /**
+ * Returns the whitelisted proxy context paths as a list of paths. The paths have been normalized to the form {@code /some/context/path}.
+ *
+ * @return the path(s)
+ */
+ public List<String> getWhitelistedContextPathsAsList() {
+ String rawProperty = getProperty(WEB_PROXY_CONTEXT_PATH, "");
+ List<String> contextPaths = Arrays.asList(rawProperty.split(","));
+ return contextPaths.stream()
+ .map(this::normalizeContextPath).collect(Collectors.toList());
+ }
+
+ private String normalizeContextPath(String cp) {
+ if (cp == null || cp.equalsIgnoreCase("")) {
+ return "";
+ } else {
+ String trimmedCP = cp.trim();
+ // Ensure it starts with a leading slash and does not end in a trailing slash
+ // There's a potential for the path to be something like bad/path/// but this is semi-trusted data from an admin-accessible file and there are way worse possibilities here
+ trimmedCP = trimmedCP.startsWith("/") ? trimmedCP : "/" + trimmedCP;
+ trimmedCP = trimmedCP.endsWith("/") ? trimmedCP.substring(0, trimmedCP.length() - 1) : trimmedCP;
+ return trimmedCP;
+ }
+ }
+
+ private List<String> getProvenanceRepositoryEncryptionKeyProperties() {
+ // Filter all the property keys that define a key
+ return getPropertyKeys().stream().filter(k ->
+ k.startsWith(PROVENANCE_REPO_ENCRYPTION_KEY_ID + ".") || k.equalsIgnoreCase(PROVENANCE_REPO_ENCRYPTION_KEY)
+ ).collect(Collectors.toList());
+ }
+
+ public Long getDefaultBackPressureObjectThreshold() {
+ long backPressureCount;
+ try {
+ String backPressureCountStr = getProperty(BACKPRESSURE_COUNT);
+ if (backPressureCountStr == null || backPressureCountStr.trim().isEmpty()) {
+ backPressureCount = DEFAULT_BACKPRESSURE_COUNT;
+ } else {
+ backPressureCount = Long.parseLong(backPressureCountStr);
+ }
+ } catch (NumberFormatException nfe) {
+ backPressureCount = DEFAULT_BACKPRESSURE_COUNT;
+ }
+ return backPressureCount;
+ }
+
+ public String getDefaultBackPressureDataSizeThreshold() {
+ return getProperty(BACKPRESSURE_SIZE, DEFAULT_BACKPRESSURE_SIZE);
+ }
+
+ /**
+ * Creates an instance of NiFiProperties. This should likely not be called
+ * by any classes outside of the NiFi framework but can be useful by the
+ * framework for default property loading behavior or helpful in tests
+ * needing to create specific instances of NiFiProperties. If properties
+ * file specified cannot be found/read a runtime exception will be thrown.
+ * If one is not specified no properties will be loaded by default.
+ *
+ * @param propertiesFilePath if provided properties will be loaded from
+ * given file; else will be loaded from System property. Can be null.
+ * @param additionalProperties allows overriding of properties with the
+ * supplied values. these will be applied after loading from any properties
+ * file. Can be null or empty.
+ * @return NiFiProperties
+ */
+ public static NiFiProperties createBasicNiFiProperties(final String propertiesFilePath, final Map<String, String> additionalProperties) {
+ final Map<String, String> addProps = (additionalProperties == null) ? Collections.EMPTY_MAP : additionalProperties;
+ final Properties properties = new Properties();
+ final String nfPropertiesFilePath = (propertiesFilePath == null)
+ ? System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
+ : propertiesFilePath;
+ if (nfPropertiesFilePath != null) {
+ final File propertiesFile = new File(nfPropertiesFilePath.trim());
+ if (!propertiesFile.exists()) {
+ throw new RuntimeException("Properties file doesn't exist \'"
+ + propertiesFile.getAbsolutePath() + "\'");
+ }
+ if (!propertiesFile.canRead()) {
+ throw new RuntimeException("Properties file exists but cannot be read \'"
+ + propertiesFile.getAbsolutePath() + "\'");
+ }
+ InputStream inStream = null;
+ try {
+ inStream = new BufferedInputStream(new FileInputStream(propertiesFile));
+ properties.load(inStream);
+ } catch (final Exception ex) {
+ throw new RuntimeException("Cannot load properties file due to "
+ + ex.getLocalizedMessage(), ex);
+ } finally {
+ if (null != inStream) {
+ try {
+ inStream.close();
+ } catch (final Exception ex) {
+ /**
+ * do nothing *
+ */
+ }
+ }
+ }
+ }
+ addProps.entrySet().stream().forEach((entry) -> {
+ properties.setProperty(entry.getKey(), entry.getValue());
+ });
+ return new NiFiProperties() {
+ @Override
+ public String getProperty(String key) {
+ return properties.getProperty(key);
+ }
+
+ @Override
+ public Set<String> getPropertyKeys() {
+ return properties.stringPropertyNames();
+ }
+ };
+ }
+
+ /**
+ * This method is used to validate the NiFi properties when the file is loaded
+ * for the first time. The objective is to stop NiFi startup in case a property
+ * is not correctly configured and could cause issues afterwards.
+ */
+ public void validate() {
+ // REMOTE_INPUT_HOST should be a valid hostname
+ String remoteInputHost = getProperty(REMOTE_INPUT_HOST);
+ if (!StringUtils.isBlank(remoteInputHost) && remoteInputHost.split(":").length > 1) { // no scheme/port needed here (http://)
+ throw new IllegalArgumentException(remoteInputHost + " is not a correct value for " + REMOTE_INPUT_HOST + ". It should be a valid hostname without protocol or port.");
+ }
+ // Other properties to validate...
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
new file mode 100644
index 0000000..8ad05bd
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -0,0 +1,4899 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.web;
+
+import com.google.common.collect.Sets;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.FlowChangePurgeDetails;
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.AuthorizationResult.Result;
+import org.apache.nifi.authorization.AuthorizeAccess;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.UserContextKeys;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.EnforcePolicyPermissionsThroughBaseResource;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.resource.ResourceFactory;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.coordination.heartbeat.HeartbeatMonitor;
+import org.apache.nifi.cluster.coordination.heartbeat.NodeHeartbeat;
+import org.apache.nifi.cluster.coordination.node.ClusterRoles;
+import org.apache.nifi.cluster.coordination.node.DisconnectionCode;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
+import org.apache.nifi.cluster.coordination.node.OffloadCode;
+import org.apache.nifi.cluster.event.NodeEvent;
+import org.apache.nifi.cluster.manager.exception.IllegalNodeDeletionException;
+import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.RequiredPermission;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateMap;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.Funnel;
+import org.apache.nifi.connectable.Port;
+import org.apache.nifi.controller.ComponentNode;
+import org.apache.nifi.controller.Counter;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.controller.Snippet;
+import org.apache.nifi.controller.Template;
+import org.apache.nifi.controller.label.Label;
+import org.apache.nifi.controller.leader.election.LeaderElectionManager;
+import org.apache.nifi.controller.repository.claim.ContentDirection;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceReference;
+import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.controller.status.ProcessGroupStatus;
+import org.apache.nifi.controller.status.ProcessorStatus;
+import org.apache.nifi.diagnostics.SystemDiagnostics;
+import org.apache.nifi.events.BulletinFactory;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.groups.ProcessGroupCounts;
+import org.apache.nifi.groups.RemoteProcessGroup;
+import org.apache.nifi.history.History;
+import org.apache.nifi.history.HistoryQuery;
+import org.apache.nifi.history.PreviousValue;
+import org.apache.nifi.registry.ComponentVariableRegistry;
+import org.apache.nifi.registry.authorization.Permissions;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.flow.FlowRegistry;
+import org.apache.nifi.registry.flow.FlowRegistryClient;
+import org.apache.nifi.registry.flow.VersionControlInformation;
+import org.apache.nifi.registry.flow.VersionedComponent;
+import org.apache.nifi.registry.flow.VersionedConnection;
+import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowCoordinates;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.flow.VersionedFlowState;
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.flow.diff.ComparableDataFlow;
+import org.apache.nifi.registry.flow.diff.ConciseEvolvingDifferenceDescriptor;
+import org.apache.nifi.registry.flow.diff.DifferenceType;
+import org.apache.nifi.registry.flow.diff.FlowComparator;
+import org.apache.nifi.registry.flow.diff.FlowComparison;
+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 org.apache.nifi.registry.flow.diff.StaticDifferenceDescriptor;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedComponent;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedControllerService;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessor;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedRemoteGroupPort;
+import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.remote.RemoteGroupPort;
+import org.apache.nifi.remote.RootGroupPort;
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.BulletinQuery;
+import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.reporting.ComponentType;
+import org.apache.nifi.util.BundleUtils;
+import org.apache.nifi.util.FlowDifferenceFilters;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.api.dto.AccessPolicyDTO;
+import org.apache.nifi.web.api.dto.AccessPolicySummaryDTO;
+import org.apache.nifi.web.api.dto.AffectedComponentDTO;
+import org.apache.nifi.web.api.dto.BucketDTO;
+import org.apache.nifi.web.api.dto.BulletinBoardDTO;
+import org.apache.nifi.web.api.dto.BulletinDTO;
+import org.apache.nifi.web.api.dto.BulletinQueryDTO;
+import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ClusterDTO;
+import org.apache.nifi.web.api.dto.ComponentDTO;
+import org.apache.nifi.web.api.dto.ComponentDifferenceDTO;
+import org.apache.nifi.web.api.dto.ComponentHistoryDTO;
+import org.apache.nifi.web.api.dto.ComponentReferenceDTO;
+import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO;
+import org.apache.nifi.web.api.dto.ComponentStateDTO;
+import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.ControllerConfigurationDTO;
+import org.apache.nifi.web.api.dto.ControllerDTO;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
+import org.apache.nifi.web.api.dto.CounterDTO;
+import org.apache.nifi.web.api.dto.CountersDTO;
+import org.apache.nifi.web.api.dto.CountersSnapshotDTO;
+import org.apache.nifi.web.api.dto.DocumentedTypeDTO;
+import org.apache.nifi.web.api.dto.DropRequestDTO;
+import org.apache.nifi.web.api.dto.DtoFactory;
+import org.apache.nifi.web.api.dto.EntityFactory;
+import org.apache.nifi.web.api.dto.FlowConfigurationDTO;
+import org.apache.nifi.web.api.dto.FlowFileDTO;
+import org.apache.nifi.web.api.dto.FlowSnippetDTO;
+import org.apache.nifi.web.api.dto.FunnelDTO;
+import org.apache.nifi.web.api.dto.LabelDTO;
+import org.apache.nifi.web.api.dto.ListingRequestDTO;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.PortDTO;
+import org.apache.nifi.web.api.dto.PreviousValueDTO;
+import org.apache.nifi.web.api.dto.ProcessGroupDTO;
+import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
+import org.apache.nifi.web.api.dto.ProcessorDTO;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PropertyHistoryDTO;
+import org.apache.nifi.web.api.dto.RegistryDTO;
+import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
+import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.dto.RequiredPermissionDTO;
+import org.apache.nifi.web.api.dto.ResourceDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.SnippetDTO;
+import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.TemplateDTO;
+import org.apache.nifi.web.api.dto.UserDTO;
+import org.apache.nifi.web.api.dto.UserGroupDTO;
+import org.apache.nifi.web.api.dto.VariableRegistryDTO;
+import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
+import org.apache.nifi.web.api.dto.VersionedFlowDTO;
+import org.apache.nifi.web.api.dto.action.HistoryDTO;
+import org.apache.nifi.web.api.dto.action.HistoryQueryDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ConnectionDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ControllerServiceDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ProcessorDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.dto.provenance.ProvenanceDTO;
+import org.apache.nifi.web.api.dto.provenance.ProvenanceEventDTO;
+import org.apache.nifi.web.api.dto.provenance.ProvenanceOptionsDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.LineageDTO;
+import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
+import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
+import org.apache.nifi.web.api.dto.status.ControllerStatusDTO;
+import org.apache.nifi.web.api.dto.status.NodeProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.PortStatusDTO;
+import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
+import org.apache.nifi.web.api.dto.status.ProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
+import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
+import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
+import org.apache.nifi.web.api.entity.ActionEntity;
+import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
+import org.apache.nifi.web.api.entity.AffectedComponentEntity;
+import org.apache.nifi.web.api.entity.BucketEntity;
+import org.apache.nifi.web.api.entity.BulletinEntity;
+import org.apache.nifi.web.api.entity.ComponentReferenceEntity;
+import org.apache.nifi.web.api.entity.ConnectionEntity;
+import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ControllerBulletinsEntity;
+import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
+import org.apache.nifi.web.api.entity.CurrentUserEntity;
+import org.apache.nifi.web.api.entity.FlowComparisonEntity;
+import org.apache.nifi.web.api.entity.FlowConfigurationEntity;
+import org.apache.nifi.web.api.entity.FlowEntity;
+import org.apache.nifi.web.api.entity.FunnelEntity;
+import org.apache.nifi.web.api.entity.LabelEntity;
+import org.apache.nifi.web.api.entity.PortEntity;
+import org.apache.nifi.web.api.entity.PortStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.ProcessorDiagnosticsEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.api.entity.ProcessorStatusEntity;
+import org.apache.nifi.web.api.entity.RegistryClientEntity;
+import org.apache.nifi.web.api.entity.RegistryEntity;
+import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
+import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
+import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
+import org.apache.nifi.web.api.entity.SnippetEntity;
+import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
+import org.apache.nifi.web.api.entity.StatusHistoryEntity;
+import org.apache.nifi.web.api.entity.TemplateEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.UserEntity;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+import org.apache.nifi.web.api.entity.VariableEntity;
+import org.apache.nifi.web.api.entity.VariableRegistryEntity;
+import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity;
+import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
+import org.apache.nifi.web.api.entity.VersionedFlowEntity;
+import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataEntity;
+import org.apache.nifi.web.controller.ControllerFacade;
+import org.apache.nifi.web.dao.AccessPolicyDAO;
+import org.apache.nifi.web.dao.ConnectionDAO;
+import org.apache.nifi.web.dao.ControllerServiceDAO;
+import org.apache.nifi.web.dao.FunnelDAO;
+import org.apache.nifi.web.dao.LabelDAO;
+import org.apache.nifi.web.dao.PortDAO;
+import org.apache.nifi.web.dao.ProcessGroupDAO;
+import org.apache.nifi.web.dao.ProcessorDAO;
+import org.apache.nifi.web.dao.RegistryDAO;
+import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
+import org.apache.nifi.web.dao.ReportingTaskDAO;
+import org.apache.nifi.web.dao.SnippetDAO;
+import org.apache.nifi.web.dao.TemplateDAO;
+import org.apache.nifi.web.dao.UserDAO;
+import org.apache.nifi.web.dao.UserGroupDAO;
+import org.apache.nifi.web.revision.DeleteRevisionTask;
+import org.apache.nifi.web.revision.ExpiredRevisionClaimException;
+import org.apache.nifi.web.revision.RevisionClaim;
+import org.apache.nifi.web.revision.RevisionManager;
+import org.apache.nifi.web.revision.RevisionUpdate;
+import org.apache.nifi.web.revision.StandardRevisionClaim;
+import org.apache.nifi.web.revision.StandardRevisionUpdate;
+import org.apache.nifi.web.revision.UpdateRevisionTask;
+import org.apache.nifi.web.util.SnippetUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Implementation of NiFiServiceFacade that performs revision checking.
+ */
+public class StandardNiFiServiceFacade implements NiFiServiceFacade {
+ private static final Logger logger = LoggerFactory.getLogger(StandardNiFiServiceFacade.class);
+ private static final int VALIDATION_WAIT_MILLIS = 50;
+
+ // nifi core components
+ private ControllerFacade controllerFacade;
+ private SnippetUtils snippetUtils;
+
+ // revision manager
+ private RevisionManager revisionManager;
+ private BulletinRepository bulletinRepository;
+
+ // data access objects
+ private ProcessorDAO processorDAO;
+ private ProcessGroupDAO processGroupDAO;
+ private RemoteProcessGroupDAO remoteProcessGroupDAO;
+ private LabelDAO labelDAO;
+ private FunnelDAO funnelDAO;
+ private SnippetDAO snippetDAO;
+ private PortDAO inputPortDAO;
+ private PortDAO outputPortDAO;
+ private ConnectionDAO connectionDAO;
+ private ControllerServiceDAO controllerServiceDAO;
+ private ReportingTaskDAO reportingTaskDAO;
+ private TemplateDAO templateDAO;
+ private UserDAO userDAO;
+ private UserGroupDAO userGroupDAO;
+ private AccessPolicyDAO accessPolicyDAO;
+ private RegistryDAO registryDAO;
+ private ClusterCoordinator clusterCoordinator;
+ private HeartbeatMonitor heartbeatMonitor;
+ private LeaderElectionManager leaderElectionManager;
+
+ // administrative services
+ private AuditService auditService;
+
+ // flow registry
+ private FlowRegistryClient flowRegistryClient;
+
+ // properties
+ private NiFiProperties properties;
+ private DtoFactory dtoFactory;
+ private EntityFactory entityFactory;
+
+ private Authorizer authorizer;
+
+ private AuthorizableLookup authorizableLookup;
+
+ // -----------------------------------------
+ // Synchronization methods
+ // -----------------------------------------
+ @Override
+ public void authorizeAccess(final AuthorizeAccess authorizeAccess) {
+ authorizeAccess.authorize(authorizableLookup);
+ }
+
+ @Override
+ public void verifyRevision(final Revision revision, final NiFiUser user) {
+ final Revision curRevision = revisionManager.getRevision(revision.getComponentId());
+ if (revision.equals(curRevision)) {
+ return;
+ }
+
+ throw new InvalidRevisionException(revision + " is not the most up-to-date revision. This component appears to have been modified");
+ }
+
+ @Override
+ public void verifyRevisions(final Set<Revision> revisions, final NiFiUser user) {
+ for (final Revision revision : revisions) {
+ verifyRevision(revision, user);
+ }
+ }
+
+ @Override
+ public Set<Revision> getRevisionsFromGroup(final String groupId, final Function<ProcessGroup, Set<String>> getComponents) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ final Set<String> componentIds = getComponents.apply(group);
+ return componentIds.stream().map(id -> revisionManager.getRevision(id)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Revision> getRevisionsFromSnippet(final String snippetId) {
+ final Snippet snippet = snippetDAO.getSnippet(snippetId);
+ final Set<String> componentIds = new HashSet<>();
+ componentIds.addAll(snippet.getProcessors().keySet());
+ componentIds.addAll(snippet.getFunnels().keySet());
+ componentIds.addAll(snippet.getLabels().keySet());
+ componentIds.addAll(snippet.getConnections().keySet());
+ componentIds.addAll(snippet.getInputPorts().keySet());
+ componentIds.addAll(snippet.getOutputPorts().keySet());
+ componentIds.addAll(snippet.getProcessGroups().keySet());
+ componentIds.addAll(snippet.getRemoteProcessGroups().keySet());
+ return componentIds.stream().map(id -> revisionManager.getRevision(id)).collect(Collectors.toSet());
+ }
+
+ // -----------------------------------------
+ // Verification Operations
+ // -----------------------------------------
+
+ @Override
+ public void verifyListQueue(final String connectionId) {
+ connectionDAO.verifyList(connectionId);
+ }
+
+ @Override
+ public void verifyCreateConnection(final String groupId, final ConnectionDTO connectionDTO) {
+ connectionDAO.verifyCreate(groupId, connectionDTO);
+ }
+
+ @Override
+ public void verifyUpdateConnection(final ConnectionDTO connectionDTO) {
+ // if connection does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (connectionDAO.hasConnection(connectionDTO.getId())) {
+ connectionDAO.verifyUpdate(connectionDTO);
+ } else {
+ connectionDAO.verifyCreate(connectionDTO.getParentGroupId(), connectionDTO);
+ }
+ }
+
+ @Override
+ public void verifyDeleteConnection(final String connectionId) {
+ connectionDAO.verifyDelete(connectionId);
+ }
+
+ @Override
+ public void verifyDeleteFunnel(final String funnelId) {
+ funnelDAO.verifyDelete(funnelId);
+ }
+
+ @Override
+ public void verifyUpdateInputPort(final PortDTO inputPortDTO) {
+ // if connection does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (inputPortDAO.hasPort(inputPortDTO.getId())) {
+ inputPortDAO.verifyUpdate(inputPortDTO);
+ }
+ }
+
+ @Override
+ public void verifyDeleteInputPort(final String inputPortId) {
+ inputPortDAO.verifyDelete(inputPortId);
+ }
+
+ @Override
+ public void verifyUpdateOutputPort(final PortDTO outputPortDTO) {
+ // if connection does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (outputPortDAO.hasPort(outputPortDTO.getId())) {
+ outputPortDAO.verifyUpdate(outputPortDTO);
+ }
+ }
+
+ @Override
+ public void verifyDeleteOutputPort(final String outputPortId) {
+ outputPortDAO.verifyDelete(outputPortId);
+ }
+
+ @Override
+ public void verifyCreateProcessor(ProcessorDTO processorDTO) {
+ processorDAO.verifyCreate(processorDTO);
+ }
+
+ @Override
+ public void verifyUpdateProcessor(final ProcessorDTO processorDTO) {
+ // if group does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (processorDAO.hasProcessor(processorDTO.getId())) {
+ processorDAO.verifyUpdate(processorDTO);
+ } else {
+ verifyCreateProcessor(processorDTO);
+ }
+ }
+
+ @Override
+ public void verifyDeleteProcessor(final String processorId) {
+ processorDAO.verifyDelete(processorId);
+ }
+
+ @Override
+ public void verifyScheduleComponents(final String groupId, final ScheduledState state, final Set<String> componentIds) {
+ processGroupDAO.verifyScheduleComponents(groupId, state, componentIds);
+ }
+
+ @Override
+ public void verifyEnableComponents(String processGroupId, ScheduledState state, Set<String> componentIds) {
+ processGroupDAO.verifyEnableComponents(processGroupId, state, componentIds);
+ }
+
+ @Override
+ public void verifyActivateControllerServices(final String groupId, final ControllerServiceState state, final Collection<String> serviceIds) {
+ processGroupDAO.verifyActivateControllerServices(state, serviceIds);
+ }
+
+ @Override
+ public void verifyDeleteProcessGroup(final String groupId) {
+ processGroupDAO.verifyDelete(groupId);
+ }
+
+ @Override
+ public void verifyUpdateRemoteProcessGroup(final RemoteProcessGroupDTO remoteProcessGroupDTO) {
+ // if remote group does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (remoteProcessGroupDAO.hasRemoteProcessGroup(remoteProcessGroupDTO.getId())) {
+ remoteProcessGroupDAO.verifyUpdate(remoteProcessGroupDTO);
+ }
+ }
+
+ @Override
+ public void verifyUpdateRemoteProcessGroupInputPort(final String remoteProcessGroupId, final RemoteProcessGroupPortDTO remoteProcessGroupPortDTO) {
+ remoteProcessGroupDAO.verifyUpdateInputPort(remoteProcessGroupId, remoteProcessGroupPortDTO);
+ }
+
+ @Override
+ public void verifyUpdateRemoteProcessGroupOutputPort(final String remoteProcessGroupId, final RemoteProcessGroupPortDTO remoteProcessGroupPortDTO) {
+ remoteProcessGroupDAO.verifyUpdateOutputPort(remoteProcessGroupId, remoteProcessGroupPortDTO);
+ }
+
+ @Override
+ public void verifyDeleteRemoteProcessGroup(final String remoteProcessGroupId) {
+ remoteProcessGroupDAO.verifyDelete(remoteProcessGroupId);
+ }
+
+ @Override
+ public void verifyCreateControllerService(ControllerServiceDTO controllerServiceDTO) {
+ controllerServiceDAO.verifyCreate(controllerServiceDTO);
+ }
+
+ @Override
+ public void verifyUpdateControllerService(final ControllerServiceDTO controllerServiceDTO) {
+ // if service does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (controllerServiceDAO.hasControllerService(controllerServiceDTO.getId())) {
+ controllerServiceDAO.verifyUpdate(controllerServiceDTO);
+ } else {
+ verifyCreateControllerService(controllerServiceDTO);
+ }
+ }
+
+ @Override
+ public void verifyUpdateControllerServiceReferencingComponents(final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) {
+ controllerServiceDAO.verifyUpdateReferencingComponents(controllerServiceId, scheduledState, controllerServiceState);
+ }
+
+ @Override
+ public void verifyDeleteControllerService(final String controllerServiceId) {
+ controllerServiceDAO.verifyDelete(controllerServiceId);
+ }
+
+ @Override
+ public void verifyCreateReportingTask(ReportingTaskDTO reportingTaskDTO) {
+ reportingTaskDAO.verifyCreate(reportingTaskDTO);
+ }
+
+ @Override
+ public void verifyUpdateReportingTask(final ReportingTaskDTO reportingTaskDTO) {
+ // if tasks does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (reportingTaskDAO.hasReportingTask(reportingTaskDTO.getId())) {
+ reportingTaskDAO.verifyUpdate(reportingTaskDTO);
+ } else {
+ verifyCreateReportingTask(reportingTaskDTO);
+ }
+ }
+
+ @Override
+ public void verifyDeleteReportingTask(final String reportingTaskId) {
+ reportingTaskDAO.verifyDelete(reportingTaskId);
+ }
+
+ // -----------------------------------------
+ // Write Operations
+ // -----------------------------------------
+
+ @Override
+ public AccessPolicyEntity updateAccessPolicy(final Revision revision, final AccessPolicyDTO accessPolicyDTO) {
+ final Authorizable authorizable = authorizableLookup.getAccessPolicyById(accessPolicyDTO.getId());
+ final RevisionUpdate<AccessPolicyDTO> snapshot = updateComponent(revision,
+ authorizable,
+ () -> accessPolicyDAO.updateAccessPolicy(accessPolicyDTO),
+ accessPolicy -> {
+ final Set<TenantEntity> users = accessPolicy.getUsers().stream().map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet());
+ final Set<TenantEntity> userGroups = accessPolicy.getGroups().stream().map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet());
+ final ComponentReferenceEntity componentReference = createComponentReferenceEntity(accessPolicy.getResource());
+ return dtoFactory.createAccessPolicyDto(accessPolicy, userGroups, users, componentReference);
+ });
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizable);
+ return entityFactory.createAccessPolicyEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public UserEntity updateUser(final Revision revision, final UserDTO userDTO) {
+ final Authorizable usersAuthorizable = authorizableLookup.getTenant();
+ final Set<Group> groups = userGroupDAO.getUserGroupsForUser(userDTO.getId());
+ final Set<AccessPolicy> policies = userGroupDAO.getAccessPoliciesForUser(userDTO.getId());
+ final RevisionUpdate<UserDTO> snapshot = updateComponent(revision,
+ usersAuthorizable,
+ () -> userDAO.updateUser(userDTO),
+ user -> {
+ final Set<TenantEntity> tenantEntities = groups.stream().map(g -> g.getIdentifier()).map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = policies.stream().map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ return dtoFactory.createUserDto(user, tenantEntities, policyEntities);
+ });
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(usersAuthorizable);
+ return entityFactory.createUserEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public UserGroupEntity updateUserGroup(final Revision revision, final UserGroupDTO userGroupDTO) {
+ final Authorizable userGroupsAuthorizable = authorizableLookup.getTenant();
+ final Set<AccessPolicy> policies = userGroupDAO.getAccessPoliciesForUserGroup(userGroupDTO.getId());
+ final RevisionUpdate<UserGroupDTO> snapshot = updateComponent(revision,
+ userGroupsAuthorizable,
+ () -> userGroupDAO.updateUserGroup(userGroupDTO),
+ userGroup -> {
+ final Set<TenantEntity> tenantEntities = userGroup.getUsers().stream().map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = policies.stream().map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ return dtoFactory.createUserGroupDto(userGroup, tenantEntities, policyEntities);
+ }
+ );
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(userGroupsAuthorizable);
+ return entityFactory.createUserGroupEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public ConnectionEntity updateConnection(final Revision revision, final ConnectionDTO connectionDTO) {
+ final Connection connectionNode = connectionDAO.getConnection(connectionDTO.getId());
+
+ final RevisionUpdate<ConnectionDTO> snapshot = updateComponent(
+ revision,
+ connectionNode,
+ () -> connectionDAO.updateConnection(connectionDTO),
+ connection -> dtoFactory.createConnectionDto(connection));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connectionNode);
+ final ConnectionStatusDTO status = dtoFactory.createConnectionStatusDto(controllerFacade.getConnectionStatus(connectionNode.getIdentifier()));
+ return entityFactory.createConnectionEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status);
+ }
+
+ @Override
+ public ProcessorEntity updateProcessor(final Revision revision, final ProcessorDTO processorDTO) {
+ // get the component, ensure we have access to it, and perform the update request
+ final ProcessorNode processorNode = processorDAO.getProcessor(processorDTO.getId());
+ final RevisionUpdate<ProcessorDTO> snapshot = updateComponent(revision,
+ processorNode,
+ () -> processorDAO.updateProcessor(processorDTO),
+ proc -> {
+ awaitValidationCompletion(proc);
+ return dtoFactory.createProcessorDto(proc);
+ });
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processorNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processorNode));
+ final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processorNode.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processorNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ private void awaitValidationCompletion(final ComponentNode component) {
+ component.getValidationStatus(VALIDATION_WAIT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public LabelEntity updateLabel(final Revision revision, final LabelDTO labelDTO) {
+ final Label labelNode = labelDAO.getLabel(labelDTO.getId());
+ final RevisionUpdate<LabelDTO> snapshot = updateComponent(revision,
+ labelNode,
+ () -> labelDAO.updateLabel(labelDTO),
+ label -> dtoFactory.createLabelDto(label));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(labelNode);
+ return entityFactory.createLabelEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public FunnelEntity updateFunnel(final Revision revision, final FunnelDTO funnelDTO) {
+ final Funnel funnelNode = funnelDAO.getFunnel(funnelDTO.getId());
+ final RevisionUpdate<FunnelDTO> snapshot = updateComponent(revision,
+ funnelNode,
+ () -> funnelDAO.updateFunnel(funnelDTO),
+ funnel -> dtoFactory.createFunnelDto(funnel));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(funnelNode);
+ return entityFactory.createFunnelEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+
+ /**
+ * Updates a component with the given revision, using the provided supplier to call
+ * into the appropriate DAO and the provided function to convert the component into a DTO.
+ *
+ * @param revision the current revision
+ * @param daoUpdate a Supplier that will update the component via the appropriate DAO
+ * @param dtoCreation a Function to convert a component into a dao
+ * @param <D> the DTO Type of the updated component
+ * @param <C> the Component Type of the updated component
+ * @return A RevisionUpdate that represents the new configuration
+ */
+ private <D, C> RevisionUpdate<D> updateComponent(final Revision revision, final Authorizable authorizable, final Supplier<C> daoUpdate, final Function<C, D> dtoCreation) {
+ try {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final RevisionUpdate<D> updatedComponent = revisionManager.updateRevision(new StandardRevisionClaim(revision), user, new UpdateRevisionTask<D>() {
+ @Override
+ public RevisionUpdate<D> update() {
+ // get the updated component
+ final C component = daoUpdate.get();
+
+ // save updated controller
+ controllerFacade.save();
+
+ final D dto = dtoCreation.apply(component);
+
+ final Revision updatedRevision = revisionManager.getRevision(revision.getComponentId()).incrementRevision(revision.getClientId());
+ final FlowModification lastModification = new FlowModification(updatedRevision, user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastModification);
+ }
+ });
+
+ return updatedComponent;
+ } catch (final ExpiredRevisionClaimException erce) {
+ throw new InvalidRevisionException("Failed to update component " + authorizable, erce);
+ }
+ }
+
+
+ @Override
+ public void verifyUpdateSnippet(final SnippetDTO snippetDto, final Set<String> affectedComponentIds) {
+ // if snippet does not exist, then the update request is likely creating it
+ // so we don't verify since it will fail
+ if (snippetDAO.hasSnippet(snippetDto.getId())) {
+ snippetDAO.verifyUpdateSnippetComponent(snippetDto);
+ }
+ }
+
+ @Override
+ public SnippetEntity updateSnippet(final Set<Revision> revisions, final SnippetDTO snippetDto) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim revisionClaim = new StandardRevisionClaim(revisions);
+
+ final RevisionUpdate<SnippetDTO> snapshot;
+ try {
+ snapshot = revisionManager.updateRevision(revisionClaim, user, new UpdateRevisionTask<SnippetDTO>() {
+ @Override
+ public RevisionUpdate<SnippetDTO> update() {
+ // get the updated component
+ final Snippet snippet = snippetDAO.updateSnippetComponents(snippetDto);
+
+ // drop the snippet
+ snippetDAO.dropSnippet(snippet.getId());
+
+ // save updated controller
+ controllerFacade.save();
+
+ // increment the revisions
+ final Set<Revision> updatedRevisions = revisions.stream().map(revision -> {
+ final Revision currentRevision = revisionManager.getRevision(revision.getComponentId());
+ return currentRevision.incrementRevision(revision.getClientId());
+ }).collect(Collectors.toSet());
+
+ final SnippetDTO dto = dtoFactory.createSnippetDto(snippet);
+ return new StandardRevisionUpdate<>(dto, null, updatedRevisions);
+ }
+ });
+ } catch (final ExpiredRevisionClaimException e) {
+ throw new InvalidRevisionException("Failed to update Snippet", e);
+ }
+
+ return entityFactory.createSnippetEntity(snapshot.getComponent());
+ }
+
+ @Override
+ public PortEntity updateInputPort(final Revision revision, final PortDTO inputPortDTO) {
+ final Port inputPortNode = inputPortDAO.getPort(inputPortDTO.getId());
+ final RevisionUpdate<PortDTO> snapshot = updateComponent(revision,
+ inputPortNode,
+ () -> inputPortDAO.updatePort(inputPortDTO),
+ port -> dtoFactory.createPortDto(port));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(inputPortNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(inputPortNode));
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(inputPortNode.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPortNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public PortEntity updateOutputPort(final Revision revision, final PortDTO outputPortDTO) {
+ final Port outputPortNode = outputPortDAO.getPort(outputPortDTO.getId());
+ final RevisionUpdate<PortDTO> snapshot = updateComponent(revision,
+ outputPortNode,
+ () -> outputPortDAO.updatePort(outputPortDTO),
+ port -> dtoFactory.createPortDto(port));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(outputPortNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(outputPortNode), NiFiUserUtils.getNiFiUser());
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(outputPortNode.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPortNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public RemoteProcessGroupEntity updateRemoteProcessGroup(final Revision revision, final RemoteProcessGroupDTO remoteProcessGroupDTO) {
+ final RemoteProcessGroup remoteProcessGroupNode = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupDTO.getId());
+ final RevisionUpdate<RemoteProcessGroupDTO> snapshot = updateComponent(
+ revision,
+ remoteProcessGroupNode,
+ () -> remoteProcessGroupDAO.updateRemoteProcessGroup(remoteProcessGroupDTO),
+ remoteProcessGroup -> dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
+ final RevisionDTO updateRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
+ final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroupNode,
+ controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroupNode.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroupNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), updateRevision, permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public RemoteProcessGroupPortEntity updateRemoteProcessGroupInputPort(
+ final Revision revision, final String remoteProcessGroupId, final RemoteProcessGroupPortDTO remoteProcessGroupPortDTO) {
+
+ final RemoteProcessGroup remoteProcessGroupNode = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupPortDTO.getGroupId());
+ final RevisionUpdate<RemoteProcessGroupPortDTO> snapshot = updateComponent(
+ revision,
+ remoteProcessGroupNode,
+ () -> remoteProcessGroupDAO.updateRemoteProcessGroupInputPort(remoteProcessGroupId, remoteProcessGroupPortDTO),
+ remoteGroupPort -> dtoFactory.createRemoteProcessGroupPortDto(remoteGroupPort));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
+ final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
+ return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions, operatePermissions);
+ }
+
+ @Override
+ public RemoteProcessGroupPortEntity updateRemoteProcessGroupOutputPort(
+ final Revision revision, final String remoteProcessGroupId, final RemoteProcessGroupPortDTO remoteProcessGroupPortDTO) {
+
+ final RemoteProcessGroup remoteProcessGroupNode = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupPortDTO.getGroupId());
+ final RevisionUpdate<RemoteProcessGroupPortDTO> snapshot = updateComponent(
+ revision,
+ remoteProcessGroupNode,
+ () -> remoteProcessGroupDAO.updateRemoteProcessGroupOutputPort(remoteProcessGroupId, remoteProcessGroupPortDTO),
+ remoteGroupPort -> dtoFactory.createRemoteProcessGroupPortDto(remoteGroupPort));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
+ final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
+ return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions, operatePermissions);
+ }
+
+ @Override
+ public Set<AffectedComponentDTO> getActiveComponentsAffectedByVariableRegistryUpdate(final VariableRegistryDTO variableRegistryDto) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(variableRegistryDto.getProcessGroupId());
+ if (group == null) {
+ throw new ResourceNotFoundException("Could not find Process Group with ID " + variableRegistryDto.getProcessGroupId());
+ }
+
+ final Map<String, String> variableMap = new HashMap<>();
+ variableRegistryDto.getVariables().stream() // have to use forEach here instead of using Collectors.toMap because value may be null
+ .map(VariableEntity::getVariable)
+ .forEach(var -> variableMap.put(var.getName(), var.getValue()));
+
+ final Set<AffectedComponentDTO> affectedComponentDtos = new HashSet<>();
+
+ final Set<String> updatedVariableNames = getUpdatedVariables(group, variableMap);
+ for (final String variableName : updatedVariableNames) {
+ final Set<ComponentNode> affectedComponents = group.getComponentsAffectedByVariable(variableName);
+
+ for (final ComponentNode component : affectedComponents) {
+ if (component instanceof ProcessorNode) {
+ final ProcessorNode procNode = (ProcessorNode) component;
+ if (procNode.isRunning()) {
+ affectedComponentDtos.add(dtoFactory.createAffectedComponentDto(procNode));
+ }
+ } else if (component instanceof ControllerServiceNode) {
+ final ControllerServiceNode serviceNode = (ControllerServiceNode) component;
+ if (serviceNode.isActive()) {
+ affectedComponentDtos.add(dtoFactory.createAffectedComponentDto(serviceNode));
+ }
+ } else {
+ throw new RuntimeException("Found unexpected type of Component [" + component.getCanonicalClassName() + "] dependending on variable");
+ }
+ }
+ }
+
+ return affectedComponentDtos;
+ }
+
+ @Override
+ public Set<AffectedComponentEntity> getComponentsAffectedByVariableRegistryUpdate(final VariableRegistryDTO variableRegistryDto) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(variableRegistryDto.getProcessGroupId());
+ if (group == null) {
+ throw new ResourceNotFoundException("Could not find Process Group with ID " + variableRegistryDto.getProcessGroupId());
+ }
+
+ final Map<String, String> variableMap = new HashMap<>();
+ variableRegistryDto.getVariables().stream() // have to use forEach here instead of using Collectors.toMap because value may be null
+ .map(VariableEntity::getVariable)
+ .forEach(var -> variableMap.put(var.getName(), var.getValue()));
+
+ final Set<AffectedComponentEntity> affectedComponentEntities = new HashSet<>();
+
+ final Set<String> updatedVariableNames = getUpdatedVariables(group, variableMap);
+ for (final String variableName : updatedVariableNames) {
+ final Set<ComponentNode> affectedComponents = group.getComponentsAffectedByVariable(variableName);
+ affectedComponentEntities.addAll(dtoFactory.createAffectedComponentEntities(affectedComponents, revisionManager));
+ }
+
+ return affectedComponentEntities;
+ }
+
+ private Set<String> getUpdatedVariables(final ProcessGroup group, final Map<String, String> newVariableValues) {
+ final Set<String> updatedVariableNames = new HashSet<>();
+
+ final ComponentVariableRegistry registry = group.getVariableRegistry();
+ for (final Map.Entry<String, String> entry : newVariableValues.entrySet()) {
+ final String varName = entry.getKey();
+ final String newValue = entry.getValue();
+
+ final String curValue = registry.getVariableValue(varName);
+ if (!Objects.equals(newValue, curValue)) {
+ updatedVariableNames.add(varName);
+ }
+ }
+
+ return updatedVariableNames;
+ }
+
+
+ @Override
+ public VariableRegistryEntity updateVariableRegistry(Revision revision, VariableRegistryDTO variableRegistryDto) {
+ final ProcessGroup processGroupNode = processGroupDAO.getProcessGroup(variableRegistryDto.getProcessGroupId());
+ final RevisionUpdate<VariableRegistryDTO> snapshot = updateComponent(revision,
+ processGroupNode,
+ () -> processGroupDAO.updateVariableRegistry(variableRegistryDto),
+ processGroup -> dtoFactory.createVariableRegistryDto(processGroup, revisionManager));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroupNode);
+ final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
+ return entityFactory.createVariableRegistryEntity(snapshot.getComponent(), updatedRevision, permissions);
+ }
+
+
+ @Override
+ public ProcessGroupEntity updateProcessGroup(final Revision revision, final ProcessGroupDTO processGroupDTO) {
+ final ProcessGroup processGroupNode = processGroupDAO.getProcessGroup(processGroupDTO.getId());
+ final RevisionUpdate<ProcessGroupDTO> snapshot = updateComponent(revision,
+ processGroupNode,
+ () -> processGroupDAO.updateProcessGroup(processGroupDTO),
+ processGroup -> dtoFactory.createProcessGroupDto(processGroup));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroupNode);
+ final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
+ final ProcessGroupStatusDTO status = dtoFactory.createConciseProcessGroupStatusDto(controllerFacade.getProcessGroupStatus(processGroupNode.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processGroupNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessGroupEntity(snapshot.getComponent(), updatedRevision, permissions, status, bulletinEntities);
+ }
+
+ @Override
+ public void verifyUpdateProcessGroup(ProcessGroupDTO processGroupDTO) {
+ if (processGroupDAO.hasProcessGroup(processGroupDTO.getId())) {
+ processGroupDAO.verifyUpdate(processGroupDTO);
+ }
+ }
+
+ @Override
+ public ScheduleComponentsEntity enableComponents(String processGroupId, ScheduledState state, Map<String, Revision> componentRevisions) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final RevisionUpdate<ScheduleComponentsEntity> updatedComponent = revisionManager.updateRevision(new StandardRevisionClaim(componentRevisions.values()), user, new
+ UpdateRevisionTask<ScheduleComponentsEntity>() {
+ @Override
+ public RevisionUpdate<ScheduleComponentsEntity> update() {
+ // schedule the components
+ processGroupDAO.enableComponents(processGroupId, state, componentRevisions.keySet());
+
+ // update the revisions
+ final Map<String, Revision> updatedRevisions = new HashMap<>();
+ for (final Revision revision : componentRevisions.values()) {
+ final Revision currentRevision = revisionManager.getRevision(revision.getComponentId());
+ updatedRevisions.put(revision.getComponentId(), currentRevision.incrementRevision(revision.getClientId()));
+ }
+
+ // save
+ controllerFacade.save();
+
+ // gather details for response
+ final ScheduleComponentsEntity entity = new ScheduleComponentsEntity();
+ entity.setId(processGroupId);
+ entity.setState(state.name());
+ return new StandardRevisionUpdate<>(entity, null, new HashSet<>(updatedRevisions.values()));
+ }
+ });
+
+ return updatedComponent.getComponent();
+ }
+
+ @Override
+ public ScheduleComponentsEntity scheduleComponents(final String processGroupId, final ScheduledState state, final Map<String, Revision> componentRevisions) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionUpdate<ScheduleComponentsEntity> updatedComponent = revisionManager.updateRevision(new StandardRevisionClaim(componentRevisions.values()), user, new
+ UpdateRevisionTask<ScheduleComponentsEntity>() {
+ @Override
+ public RevisionUpdate<ScheduleComponentsEntity> update() {
+ // schedule the components
+ processGroupDAO.scheduleComponents(processGroupId, state, componentRevisions.keySet());
+
+ // update the revisions
+ final Map<String, Revision> updatedRevisions = new HashMap<>();
+ for (final Revision revision : componentRevisions.values()) {
+ final Revision currentRevision = revisionManager.getRevision(revision.getComponentId());
+ updatedRevisions.put(revision.getComponentId(), currentRevision.incrementRevision(revision.getClientId()));
+ }
+
+ // save
+ controllerFacade.save();
+
+ // gather details for response
+ final ScheduleComponentsEntity entity = new ScheduleComponentsEntity();
+ entity.setId(processGroupId);
+ entity.setState(state.name());
+ return new StandardRevisionUpdate<>(entity, null, new HashSet<>(updatedRevisions.values()));
+ }
+ });
+
+ return updatedComponent.getComponent();
+ }
+
+ @Override
+ public ActivateControllerServicesEntity activateControllerServices(final String processGroupId, final ControllerServiceState state, final Map<String, Revision> serviceRevisions) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionUpdate<ActivateControllerServicesEntity> updatedComponent = revisionManager.updateRevision(new StandardRevisionClaim(serviceRevisions.values()), user,
+ new UpdateRevisionTask<ActivateControllerServicesEntity>() {
+ @Override
+ public RevisionUpdate<ActivateControllerServicesEntity> update() {
+ // schedule the components
+ processGroupDAO.activateControllerServices(processGroupId, state, serviceRevisions.keySet());
+
+ // update the revisions
+ final Map<String, Revision> updatedRevisions = new HashMap<>();
+ for (final Revision revision : serviceRevisions.values()) {
+ final Revision currentRevision = revisionManager.getRevision(revision.getComponentId());
+ updatedRevisions.put(revision.getComponentId(), currentRevision.incrementRevision(revision.getClientId()));
+ }
+
+ // save
+ controllerFacade.save();
+
+ // gather details for response
+ final ActivateControllerServicesEntity entity = new ActivateControllerServicesEntity();
+ entity.setId(processGroupId);
+ entity.setState(state.name());
+ return new StandardRevisionUpdate<>(entity, null, new HashSet<>(updatedRevisions.values()));
+ }
+ });
+
+ return updatedComponent.getComponent();
+ }
+
+
+ @Override
+ public ControllerConfigurationEntity updateControllerConfiguration(final Revision revision, final ControllerConfigurationDTO controllerConfigurationDTO) {
+ final RevisionUpdate<ControllerConfigurationDTO> updatedComponent = updateComponent(
+ revision,
+ controllerFacade,
+ () -> {
+ if (controllerConfigurationDTO.getMaxTimerDrivenThreadCount() != null) {
+ controllerFacade.setMaxTimerDrivenThreadCount(controllerConfigurationDTO.getMaxTimerDrivenThreadCount());
+ }
+ if (controllerConfigurationDTO.getMaxEventDrivenThreadCount() != null) {
+ controllerFacade.setMaxEventDrivenThreadCount(controllerConfigurationDTO.getMaxEventDrivenThreadCount());
+ }
+
+ return controllerConfigurationDTO;
+ },
+ controller -> dtoFactory.createControllerConfigurationDto(controllerFacade));
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerFacade);
+ final RevisionDTO updateRevision = dtoFactory.createRevisionDTO(updatedComponent.getLastModification());
+ return entityFactory.createControllerConfigurationEntity(updatedComponent.getComponent(), updateRevision, permissions);
+ }
+
+
+ @Override
+ public NodeDTO updateNode(final NodeDTO nodeDTO) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new WebApplicationException(new Throwable("Unable to access details for current user."));
+ }
+ final String userDn = user.getIdentity();
+
+ final NodeIdentifier nodeId = clusterCoordinator.getNodeIdentifier(nodeDTO.getNodeId());
+ if (nodeId == null) {
+ throw new UnknownNodeException("No node exists with ID " + nodeDTO.getNodeId());
+ }
+
+
+ if (NodeConnectionState.CONNECTING.name().equalsIgnoreCase(nodeDTO.getStatus())) {
+ clusterCoordinator.requestNodeConnect(nodeId, userDn);
+ } else if (NodeConnectionState.OFFLOADING.name().equalsIgnoreCase(nodeDTO.getStatus())) {
+ clusterCoordinator.requestNodeOffload(nodeId, OffloadCode.OFFLOADED,
+ "User " + userDn + " requested that node be offloaded");
+ } else if (NodeConnectionState.DISCONNECTING.name().equalsIgnoreCase(nodeDTO.getStatus())) {
+ clusterCoordinator.requestNodeDisconnect(nodeId, DisconnectionCode.USER_DISCONNECTED,
+ "User " + userDn + " requested that node be disconnected from cluster");
+ }
+
+ return getNode(nodeId);
+ }
+
+ @Override
+ public CounterDTO updateCounter(final String counterId) {
+ return dtoFactory.createCounterDto(controllerFacade.resetCounter(counterId));
+ }
+
+ @Override
+ public void verifyCanClearProcessorState(final String processorId) {
+ processorDAO.verifyClearState(processorId);
+ }
+
+ @Override
+ public void clearProcessorState(final String processorId) {
+ processorDAO.clearState(processorId);
+ }
+
+ @Override
+ public void verifyCanClearControllerServiceState(final String controllerServiceId) {
+ controllerServiceDAO.verifyClearState(controllerServiceId);
+ }
+
+ @Override
+ public void clearControllerServiceState(final String controllerServiceId) {
+ controllerServiceDAO.clearState(controllerServiceId);
+ }
+
+ @Override
+ public void verifyCanClearReportingTaskState(final String reportingTaskId) {
+ reportingTaskDAO.verifyClearState(reportingTaskId);
+ }
+
+ @Override
+ public void clearReportingTaskState(final String reportingTaskId) {
+ reportingTaskDAO.clearState(reportingTaskId);
+ }
+
+ @Override
+ public ConnectionEntity deleteConnection(final Revision revision, final String connectionId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connection);
+ final ConnectionDTO snapshot = deleteComponent(
+ revision,
+ connection.getResource(),
+ () -> connectionDAO.deleteConnection(connectionId),
+ false, // no policies to remove
+ dtoFactory.createConnectionDto(connection));
+
+ return entityFactory.createConnectionEntity(snapshot, null, permissions, null);
+ }
+
+ @Override
+ public DropRequestDTO deleteFlowFileDropRequest(final String connectionId, final String dropRequestId) {
+ return dtoFactory.createDropRequestDTO(connectionDAO.deleteFlowFileDropRequest(connectionId, dropRequestId));
+ }
+
+ @Override
+ public ListingRequestDTO deleteFlowFileListingRequest(final String connectionId, final String listingRequestId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final ListingRequestDTO listRequest = dtoFactory.createListingRequestDTO(connectionDAO.deleteFlowFileListingRequest(connectionId, listingRequestId));
+
+ // include whether the source and destination are running
+ if (connection.getSource() != null) {
+ listRequest.setSourceRunning(connection.getSource().isRunning());
+ }
+ if (connection.getDestination() != null) {
+ listRequest.setDestinationRunning(connection.getDestination().isRunning());
+ }
+
+ return listRequest;
+ }
+
+ @Override
+ public ProcessorEntity deleteProcessor(final Revision revision, final String processorId) {
+ final ProcessorNode processor = processorDAO.getProcessor(processorId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
+ final ProcessorDTO snapshot = deleteComponent(
+ revision,
+ processor.getResource(),
+ () -> processorDAO.deleteProcessor(processorId),
+ true,
+ dtoFactory.createProcessorDto(processor));
+
+ return entityFactory.createProcessorEntity(snapshot, null, permissions, operatePermissions, null, null);
+ }
+
+ @Override
+ public ProcessorEntity terminateProcessor(final String processorId) {
+ processorDAO.terminate(processorId);
+ return getProcessor(processorId);
+ }
+
+ @Override
+ public void verifyTerminateProcessor(final String processorId) {
+ processorDAO.verifyTerminate(processorId);
+ }
+
+ @Override
+ public LabelEntity deleteLabel(final Revision revision, final String labelId) {
+ final Label label = labelDAO.getLabel(labelId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(label);
+ final LabelDTO snapshot = deleteComponent(
+ revision,
+ label.getResource(),
+ () -> labelDAO.deleteLabel(labelId),
+ true,
+ dtoFactory.createLabelDto(label));
+
+ return entityFactory.createLabelEntity(snapshot, null, permissions);
+ }
+
+ @Override
+ public UserEntity deleteUser(final Revision revision, final String userId) {
+ final User user = userDAO.getUser(userId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ final Set<TenantEntity> userGroups = user != null ? userGroupDAO.getUserGroupsForUser(userId).stream()
+ .map(g -> g.getIdentifier()).map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet()) : null;
+ final Set<AccessPolicySummaryEntity> policyEntities = user != null ? userGroupDAO.getAccessPoliciesForUser(userId).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet()) : null;
+
+ final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userId;
+ final UserDTO snapshot = deleteComponent(
+ revision,
+ new Resource() {
+ @Override
+ public String getIdentifier() {
+ return resourceIdentifier;
+ }
+
+ @Override
+ public String getName() {
+ return resourceIdentifier;
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "User " + userId;
+ }
+ },
+ () -> userDAO.deleteUser(userId),
+ false, // no user specific policies to remove
+ dtoFactory.createUserDto(user, userGroups, policyEntities));
+
+ return entityFactory.createUserEntity(snapshot, null, permissions);
+ }
+
+ @Override
+ public UserGroupEntity deleteUserGroup(final Revision revision, final String userGroupId) {
+ final Group userGroup = userGroupDAO.getUserGroup(userGroupId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ final Set<TenantEntity> users = userGroup != null ? userGroup.getUsers().stream()
+ .map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet()) : null;
+ final Set<AccessPolicySummaryEntity> policyEntities = userGroupDAO.getAccessPoliciesForUserGroup(userGroup.getIdentifier()).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+
+ final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userGroupId;
+ final UserGroupDTO snapshot = deleteComponent(
+ revision,
+ new Resource() {
+ @Override
+ public String getIdentifier() {
+ return resourceIdentifier;
+ }
+
+ @Override
+ public String getName() {
+ return resourceIdentifier;
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "User Group " + userGroupId;
+ }
+ },
+ () -> userGroupDAO.deleteUserGroup(userGroupId),
+ false, // no user group specific policies to remove
+ dtoFactory.createUserGroupDto(userGroup, users, policyEntities));
+
+ return entityFactory.createUserGroupEntity(snapshot, null, permissions);
+ }
+
+ @Override
+ public AccessPolicyEntity deleteAccessPolicy(final Revision revision, final String accessPolicyId) {
+ final AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(accessPolicyId);
+ final ComponentReferenceEntity componentReference = createComponentReferenceEntity(accessPolicy.getResource());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getAccessPolicyById(accessPolicyId));
+ final Set<TenantEntity> userGroups = accessPolicy != null ? accessPolicy.getGroups().stream().map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet()) : null;
+ final Set<TenantEntity> users = accessPolicy != null ? accessPolicy.getUsers().stream().map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet()) : null;
+ final AccessPolicyDTO snapshot = deleteComponent(
+ revision,
+ new Resource() {
+ @Override
+ public String getIdentifier() {
+ return accessPolicy.getResource();
+ }
+
+ @Override
+ public String getName() {
+ return accessPolicy.getResource();
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "Policy " + accessPolicyId;
+ }
+ },
+ () -> accessPolicyDAO.deleteAccessPolicy(accessPolicyId),
+ false, // no need to clean up any policies as it's already been removed above
+ dtoFactory.createAccessPolicyDto(accessPolicy, userGroups, users, componentReference));
+
+ return entityFactory.createAccessPolicyEntity(snapshot, null, permissions);
+ }
+
+ @Override
+ public FunnelEntity deleteFunnel(final Revision revision, final String funnelId) {
+ final Funnel funnel = funnelDAO.getFunnel(funnelId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(funnel);
+ final FunnelDTO snapshot = deleteComponent(
+ revision,
+ funnel.getResource(),
+ () -> funnelDAO.deleteFunnel(funnelId),
+ true,
+ dtoFactory.createFunnelDto(funnel));
+
+ return entityFactory.createFunnelEntity(snapshot, null, permissions);
+ }
+
+ /**
+ * Deletes a component using the Optimistic Locking Manager
+ *
+ * @param revision the current revision
+ * @param resource the resource being removed
+ * @param deleteAction the action that deletes the component via the appropriate DAO object
+ * @param cleanUpPolicies whether or not the policies for this resource should be removed as well - not necessary when there are
+ * no component specific policies or if the policies of the component are inherited
+ * @return a dto that represents the new configuration
+ */
+ private <D, C> D deleteComponent(final Revision revision, final Resource resource, final Runnable deleteAction, final boolean cleanUpPolicies, final D dto) {
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ return revisionManager.deleteRevision(claim, user, new DeleteRevisionTask<D>() {
+ @Override
+ public D performTask() {
+ logger.debug("Attempting to delete component {} with claim {}", resource.getIdentifier(), claim);
+
+ // run the delete action
+ deleteAction.run();
+
+ // save the flow
+ controllerFacade.save();
+ logger.debug("Deletion of component {} was successful", resource.getIdentifier());
+
+ if (cleanUpPolicies) {
+ cleanUpPolicies(resource);
+ }
+
+ return dto;
+ }
+ });
+ }
+
+ /**
+ * Clean up the policies for the specified component resource.
+ *
+ * @param componentResource the resource for the component
+ */
+ private void cleanUpPolicies(final Resource componentResource) {
+ // ensure the authorizer supports configuration
+ if (accessPolicyDAO.supportsConfigurableAuthorizer()) {
+ final List<Resource> resources = new ArrayList<>();
+ resources.add(componentResource);
+ resources.add(ResourceFactory.getDataResource(componentResource));
+ resources.add(ResourceFactory.getProvenanceDataResource(componentResource));
+ resources.add(ResourceFactory.getDataTransferResource(componentResource));
+ resources.add(ResourceFactory.getPolicyResource(componentResource));
+
+ for (final Resource resource : resources) {
+ for (final RequestAction action : RequestAction.values()) {
+ try {
+ // since the component is being deleted, also delete any relevant access policies
+ final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(action, resource.getIdentifier());
+ if (readPolicy != null) {
+ accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier());
+ }
+ } catch (final Exception e) {
+ logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", action, resource.getIdentifier()), e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void verifyDeleteSnippet(final String snippetId, final Set<String> affectedComponentIds) {
+ snippetDAO.verifyDeleteSnippetComponents(snippetId);
+ }
+
+ @Override
+ public SnippetEntity deleteSnippet(final Set<Revision> revisions, final String snippetId) {
+ final Snippet snippet = snippetDAO.getSnippet(snippetId);
+
+ // grab the resources in the snippet so we can delete the policies afterwards
+ final Set<Resource> snippetResources = new HashSet<>();
+ snippet.getProcessors().keySet().forEach(id -> snippetResources.add(processorDAO.getProcessor(id).getResource()));
+ snippet.getInputPorts().keySet().forEach(id -> snippetResources.add(inputPortDAO.getPort(id).getResource()));
+ snippet.getOutputPorts().keySet().forEach(id -> snippetResources.add(outputPortDAO.getPort(id).getResource()));
+ snippet.getFunnels().keySet().forEach(id -> snippetResources.add(funnelDAO.getFunnel(id).getResource()));
+ snippet.getLabels().keySet().forEach(id -> snippetResources.add(labelDAO.getLabel(id).getResource()));
+ snippet.getRemoteProcessGroups().keySet().forEach(id -> snippetResources.add(remoteProcessGroupDAO.getRemoteProcessGroup(id).getResource()));
+ snippet.getProcessGroups().keySet().forEach(id -> {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(id);
+
+ // add the process group
+ snippetResources.add(processGroup.getResource());
+
+ // add each encapsulated component
+ processGroup.findAllProcessors().forEach(processor -> snippetResources.add(processor.getResource()));
+ processGroup.findAllInputPorts().forEach(inputPort -> snippetResources.add(inputPort.getResource()));
+ processGroup.findAllOutputPorts().forEach(outputPort -> snippetResources.add(outputPort.getResource()));
+ processGroup.findAllFunnels().forEach(funnel -> snippetResources.add(funnel.getResource()));
+ processGroup.findAllLabels().forEach(label -> snippetResources.add(label.getResource()));
+ processGroup.findAllProcessGroups().forEach(childGroup -> snippetResources.add(childGroup.getResource()));
+ processGroup.findAllRemoteProcessGroups().forEach(remoteProcessGroup -> snippetResources.add(remoteProcessGroup.getResource()));
+ processGroup.findAllTemplates().forEach(template -> snippetResources.add(template.getResource()));
+ processGroup.findAllControllerServices().forEach(controllerService -> snippetResources.add(controllerService.getResource()));
+ });
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revisions);
+ final SnippetDTO dto = revisionManager.deleteRevision(claim, user, new DeleteRevisionTask<SnippetDTO>() {
+ @Override
+ public SnippetDTO performTask() {
+ // delete the components in the snippet
+ snippetDAO.deleteSnippetComponents(snippetId);
+
+ // drop the snippet
+ snippetDAO.dropSnippet(snippetId);
+
+ // save
+ controllerFacade.save();
+
+ // create the dto for the snippet that was just removed
+ return dtoFactory.createSnippetDto(snippet);
+ }
+ });
+
+ // clean up component policies
+ snippetResources.forEach(resource -> cleanUpPolicies(resource));
+
+ return entityFactory.createSnippetEntity(dto);
+ }
+
+ @Override
+ public PortEntity deleteInputPort(final Revision revision, final String inputPortId) {
+ final Port port = inputPortDAO.getPort(inputPortId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
+ final PortDTO snapshot = deleteComponent(
+ revision,
+ port.getResource(),
+ () -> inputPortDAO.deletePort(inputPortId),
+ true,
+ dtoFactory.createPortDto(port));
+
+ return entityFactory.createPortEntity(snapshot, null, permissions, operatePermissions, null, null);
+ }
+
+ @Override
+ public PortEntity deleteOutputPort(final Revision revision, final String outputPortId) {
+ final Port port = outputPortDAO.getPort(outputPortId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
+ final PortDTO snapshot = deleteComponent(
+ revision,
+ port.getResource(),
+ () -> outputPortDAO.deletePort(outputPortId),
+ true,
+ dtoFactory.createPortDto(port));
+
+ return entityFactory.createPortEntity(snapshot, null, permissions, operatePermissions, null, null);
+ }
+
+ @Override
+ public ProcessGroupEntity deleteProcessGroup(final Revision revision, final String groupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+
+ // grab the resources in the snippet so we can delete the policies afterwards
+ final Set<Resource> groupResources = new HashSet<>();
+ processGroup.findAllProcessors().forEach(processor -> groupResources.add(processor.getResource()));
+ processGroup.findAllInputPorts().forEach(inputPort -> groupResources.add(inputPort.getResource()));
+ processGroup.findAllOutputPorts().forEach(outputPort -> groupResources.add(outputPort.getResource()));
+ processGroup.findAllFunnels().forEach(funnel -> groupResources.add(funnel.getResource()));
+ processGroup.findAllLabels().forEach(label -> groupResources.add(label.getResource()));
+ processGroup.findAllProcessGroups().forEach(childGroup -> groupResources.add(childGroup.getResource()));
+ processGroup.findAllRemoteProcessGroups().forEach(remoteProcessGroup -> groupResources.add(remoteProcessGroup.getResource()));
+ processGroup.findAllTemplates().forEach(template -> groupResources.add(template.getResource()));
+ processGroup.findAllControllerServices().forEach(controllerService -> groupResources.add(controllerService.getResource()));
+
+ final ProcessGroupDTO snapshot = deleteComponent(
+ revision,
+ processGroup.getResource(),
+ () -> processGroupDAO.deleteProcessGroup(groupId),
+ true,
+ dtoFactory.createProcessGroupDto(processGroup));
+
+ // delete all applicable component policies
+ groupResources.forEach(groupResource -> cleanUpPolicies(groupResource));
+
+ return entityFactory.createProcessGroupEntity(snapshot, null, permissions, null, null);
+ }
+
+ @Override
+ public RemoteProcessGroupEntity deleteRemoteProcessGroup(final Revision revision, final String remoteProcessGroupId) {
+ final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
+ final RemoteProcessGroupDTO snapshot = deleteComponent(
+ revision,
+ remoteProcessGroup.getResource(),
+ () -> remoteProcessGroupDAO.deleteRemoteProcessGroup(remoteProcessGroupId),
+ true,
+ dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup));
+
+ return entityFactory.createRemoteProcessGroupEntity(snapshot, null, permissions, operatePermissions, null, null);
+ }
+
+ @Override
+ public void deleteTemplate(final String id) {
+ // delete the template and save the flow
+ templateDAO.deleteTemplate(id);
+ controllerFacade.save();
+ }
+
+ @Override
+ public ConnectionEntity createConnection(final Revision revision, final String groupId, final ConnectionDTO connectionDTO) {
+ final RevisionUpdate<ConnectionDTO> snapshot = createComponent(
+ revision,
+ connectionDTO,
+ () -> connectionDAO.createConnection(groupId, connectionDTO),
+ connection -> dtoFactory.createConnectionDto(connection));
+
+ final Connection connection = connectionDAO.getConnection(connectionDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connection);
+ final ConnectionStatusDTO status = dtoFactory.createConnectionStatusDto(controllerFacade.getConnectionStatus(connectionDTO.getId()));
+ return entityFactory.createConnectionEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status);
+ }
+
+ @Override
+ public DropRequestDTO createFlowFileDropRequest(final String connectionId, final String dropRequestId) {
+ return dtoFactory.createDropRequestDTO(connectionDAO.createFlowFileDropRequest(connectionId, dropRequestId));
+ }
+
+ @Override
+ public ListingRequestDTO createFlowFileListingRequest(final String connectionId, final String listingRequestId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final ListingRequestDTO listRequest = dtoFactory.createListingRequestDTO(connectionDAO.createFlowFileListingRequest(connectionId, listingRequestId));
+
+ // include whether the source and destination are running
+ if (connection.getSource() != null) {
+ listRequest.setSourceRunning(connection.getSource().isRunning());
+ }
+ if (connection.getDestination() != null) {
+ listRequest.setDestinationRunning(connection.getDestination().isRunning());
+ }
+
+ return listRequest;
+ }
+
+ @Override
+ public ProcessorEntity createProcessor(final Revision revision, final String groupId, final ProcessorDTO processorDTO) {
+ final RevisionUpdate<ProcessorDTO> snapshot = createComponent(
+ revision,
+ processorDTO,
+ () -> processorDAO.createProcessor(groupId, processorDTO),
+ processor -> {
+ awaitValidationCompletion(processor);
+ return dtoFactory.createProcessorDto(processor);
+ });
+
+ final ProcessorNode processor = processorDAO.getProcessor(processorDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
+ final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processorDTO.getId()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processorDTO.getId()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public LabelEntity createLabel(final Revision revision, final String groupId, final LabelDTO labelDTO) {
+ final RevisionUpdate<LabelDTO> snapshot = createComponent(
+ revision,
+ labelDTO,
+ () -> labelDAO.createLabel(groupId, labelDTO),
+ label -> dtoFactory.createLabelDto(label));
+
+ final Label label = labelDAO.getLabel(labelDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(label);
+ return entityFactory.createLabelEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ /**
+ * Creates a component using the optimistic locking manager.
+ *
+ * @param componentDto the DTO that will be used to create the component
+ * @param daoCreation A Supplier that will create the NiFi Component to use
+ * @param dtoCreation a Function that will convert the NiFi Component into a corresponding DTO
+ * @param <D> the DTO Type
+ * @param <C> the NiFi Component Type
+ * @return a RevisionUpdate that represents the updated configuration
+ */
+ private <D, C> RevisionUpdate<D> createComponent(final Revision revision, final ComponentDTO componentDto, final Supplier<C> daoCreation, final Function<C, D> dtoCreation) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // read lock on the containing group
+ // request claim for component to be created... revision already verified (version == 0)
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ // update revision through revision manager
+ return revisionManager.updateRevision(claim, user, () -> {
+ // add the component
+ final C component = daoCreation.get();
+
+ // save the flow
+ controllerFacade.save();
+
+ final D dto = dtoCreation.apply(component);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+ }
+
+ @Override
+ public BulletinEntity createBulletin(final BulletinDTO bulletinDTO, final Boolean canRead){
+ final Bulletin bulletin = BulletinFactory.createBulletin(bulletinDTO.getCategory(),bulletinDTO.getLevel(),bulletinDTO.getMessage());
+ bulletinRepository.addBulletin(bulletin);
+ return entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),canRead);
+ }
+
+ @Override
+ public FunnelEntity createFunnel(final Revision revision, final String groupId, final FunnelDTO funnelDTO) {
+ final RevisionUpdate<FunnelDTO> snapshot = createComponent(
+ revision,
+ funnelDTO,
+ () -> funnelDAO.createFunnel(groupId, funnelDTO),
+ funnel -> dtoFactory.createFunnelDto(funnel));
+
+ final Funnel funnel = funnelDAO.getFunnel(funnelDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(funnel);
+ return entityFactory.createFunnelEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public AccessPolicyEntity createAccessPolicy(final Revision revision, final AccessPolicyDTO accessPolicyDTO) {
+ final Authorizable tenantAuthorizable = authorizableLookup.getTenant();
+ final String creator = NiFiUserUtils.getNiFiUserIdentity();
+
+ final AccessPolicy newAccessPolicy = accessPolicyDAO.createAccessPolicy(accessPolicyDTO);
+ final ComponentReferenceEntity componentReference = createComponentReferenceEntity(newAccessPolicy.getResource());
+ final AccessPolicyDTO newAccessPolicyDto = dtoFactory.createAccessPolicyDto(newAccessPolicy,
+ newAccessPolicy.getGroups().stream().map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet()),
+ newAccessPolicy.getUsers().stream().map(userId -> {
+ final RevisionDTO userRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(userId));
+ return entityFactory.createTenantEntity(dtoFactory.createTenantDTO(userDAO.getUser(userId)), userRevision,
+ dtoFactory.createPermissionsDto(tenantAuthorizable));
+ }).collect(Collectors.toSet()), componentReference);
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getAccessPolicyById(accessPolicyDTO.getId()));
+ return entityFactory.createAccessPolicyEntity(newAccessPolicyDto, dtoFactory.createRevisionDTO(new FlowModification(revision, creator)), permissions);
+ }
+
+ @Override
+ public UserEntity createUser(final Revision revision, final UserDTO userDTO) {
+ final String creator = NiFiUserUtils.getNiFiUserIdentity();
+ final User newUser = userDAO.createUser(userDTO);
+ final Set<TenantEntity> tenantEntities = userGroupDAO.getUserGroupsForUser(newUser.getIdentifier()).stream()
+ .map(g -> g.getIdentifier()).map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = userGroupDAO.getAccessPoliciesForUser(newUser.getIdentifier()).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ final UserDTO newUserDto = dtoFactory.createUserDto(newUser, tenantEntities, policyEntities);
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ return entityFactory.createUserEntity(newUserDto, dtoFactory.createRevisionDTO(new FlowModification(revision, creator)), permissions);
+ }
+
+ private ComponentReferenceEntity createComponentReferenceEntity(final String resource) {
+ ComponentReferenceEntity componentReferenceEntity = null;
+ try {
+ // get the component authorizable
+ Authorizable componentAuthorizable = authorizableLookup.getAuthorizableFromResource(resource);
+
+ // if this represents an authorizable whose policy permissions are enforced through the base resource,
+ // get the underlying base authorizable for the component reference
+ if (componentAuthorizable instanceof EnforcePolicyPermissionsThroughBaseResource) {
+ componentAuthorizable = ((EnforcePolicyPermissionsThroughBaseResource) componentAuthorizable).getBaseAuthorizable();
+ }
+
+ final ComponentReferenceDTO componentReference = dtoFactory.createComponentReferenceDto(componentAuthorizable);
+ if (componentReference != null) {
+ final PermissionsDTO componentReferencePermissions = dtoFactory.createPermissionsDto(componentAuthorizable);
+ final RevisionDTO componentReferenceRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(componentReference.getId()));
+ componentReferenceEntity = entityFactory.createComponentReferenceEntity(componentReference, componentReferenceRevision, componentReferencePermissions);
+ }
+ } catch (final ResourceNotFoundException e) {
+ // component not found for the specified resource
+ }
+
+ return componentReferenceEntity;
+ }
+
+ private AccessPolicySummaryEntity createAccessPolicySummaryEntity(final AccessPolicy ap) {
+ final ComponentReferenceEntity componentReference = createComponentReferenceEntity(ap.getResource());
+ final AccessPolicySummaryDTO apSummary = dtoFactory.createAccessPolicySummaryDto(ap, componentReference);
+ final PermissionsDTO apPermissions = dtoFactory.createPermissionsDto(authorizableLookup.getAccessPolicyById(ap.getIdentifier()));
+ final RevisionDTO apRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(ap.getIdentifier()));
+ return entityFactory.createAccessPolicySummaryEntity(apSummary, apRevision, apPermissions);
+ }
+
+ @Override
+ public UserGroupEntity createUserGroup(final Revision revision, final UserGroupDTO userGroupDTO) {
+ final String creator = NiFiUserUtils.getNiFiUserIdentity();
+ final Group newUserGroup = userGroupDAO.createUserGroup(userGroupDTO);
+ final Set<TenantEntity> tenantEntities = newUserGroup.getUsers().stream().map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = userGroupDAO.getAccessPoliciesForUserGroup(newUserGroup.getIdentifier()).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ final UserGroupDTO newUserGroupDto = dtoFactory.createUserGroupDto(newUserGroup, tenantEntities, policyEntities);
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ return entityFactory.createUserGroupEntity(newUserGroupDto, dtoFactory.createRevisionDTO(new FlowModification(revision, creator)), permissions);
+ }
+
+ private void validateSnippetContents(final FlowSnippetDTO flow) {
+ // validate any processors
+ if (flow.getProcessors() != null) {
+ for (final ProcessorDTO processorDTO : flow.getProcessors()) {
+ final ProcessorNode processorNode = processorDAO.getProcessor(processorDTO.getId());
+ processorDTO.setValidationStatus(processorNode.getValidationStatus().name());
+
+ final Collection<ValidationResult> validationErrors = processorNode.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+ processorDTO.setValidationErrors(errors);
+ }
+ }
+ }
+
+ if (flow.getInputPorts() != null) {
+ for (final PortDTO portDTO : flow.getInputPorts()) {
+ final Port port = inputPortDAO.getPort(portDTO.getId());
+ final Collection<ValidationResult> validationErrors = port.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+ portDTO.setValidationErrors(errors);
+ }
+ }
+ }
+
+ if (flow.getOutputPorts() != null) {
+ for (final PortDTO portDTO : flow.getOutputPorts()) {
+ final Port port = outputPortDAO.getPort(portDTO.getId());
+ final Collection<ValidationResult> validationErrors = port.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+ portDTO.setValidationErrors(errors);
+ }
+ }
+ }
+
+ // get any remote process group issues
+ if (flow.getRemoteProcessGroups() != null) {
+ for (final RemoteProcessGroupDTO remoteProcessGroupDTO : flow.getRemoteProcessGroups()) {
+ final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupDTO.getId());
+
+ if (remoteProcessGroup.getAuthorizationIssue() != null) {
+ remoteProcessGroupDTO.setAuthorizationIssues(Arrays.asList(remoteProcessGroup.getAuthorizationIssue()));
+ }
+ }
+ }
+ }
+
+ @Override
+ public FlowEntity copySnippet(final String groupId, final String snippetId, final Double originX, final Double originY, final String idGenerationSeed) {
+ // create the new snippet
+ final FlowSnippetDTO snippet = snippetDAO.copySnippet(groupId, snippetId, originX, originY, idGenerationSeed);
+
+ // save the flow
+ controllerFacade.save();
+
+ // drop the snippet
+ snippetDAO.dropSnippet(snippetId);
+
+ // post process new flow snippet
+ final FlowDTO flowDto = postProcessNewFlowSnippet(groupId, snippet);
+
+ final FlowEntity flowEntity = new FlowEntity();
+ flowEntity.setFlow(flowDto);
+ return flowEntity;
+ }
+
+ @Override
+ public SnippetEntity createSnippet(final SnippetDTO snippetDTO) {
+ // add the component
+ final Snippet snippet = snippetDAO.createSnippet(snippetDTO);
+
+ // save the flow
+ controllerFacade.save();
+
+ final SnippetDTO dto = dtoFactory.createSnippetDto(snippet);
+ final RevisionUpdate<SnippetDTO> snapshot = new StandardRevisionUpdate<>(dto, null);
+
+ return entityFactory.createSnippetEntity(snapshot.getComponent());
+ }
+
+ @Override
+ public PortEntity createInputPort(final Revision revision, final String groupId, final PortDTO inputPortDTO) {
+ final RevisionUpdate<PortDTO> snapshot = createComponent(
+ revision,
+ inputPortDTO,
+ () -> inputPortDAO.createPort(groupId, inputPortDTO),
+ port -> dtoFactory.createPortDto(port));
+
+ final Port port = inputPortDAO.getPort(inputPortDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(port.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public PortEntity createOutputPort(final Revision revision, final String groupId, final PortDTO outputPortDTO) {
+ final RevisionUpdate<PortDTO> snapshot = createComponent(
+ revision,
+ outputPortDTO,
+ () -> outputPortDAO.createPort(groupId, outputPortDTO),
+ port -> dtoFactory.createPortDto(port));
+
+ final Port port = outputPortDAO.getPort(outputPortDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(port.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public ProcessGroupEntity createProcessGroup(final Revision revision, final String parentGroupId, final ProcessGroupDTO processGroupDTO) {
+ final RevisionUpdate<ProcessGroupDTO> snapshot = createComponent(
+ revision,
+ processGroupDTO,
+ () -> processGroupDAO.createProcessGroup(parentGroupId, processGroupDTO),
+ processGroup -> dtoFactory.createProcessGroupDto(processGroup));
+
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(processGroupDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ final ProcessGroupStatusDTO status = dtoFactory.createConciseProcessGroupStatusDto(controllerFacade.getProcessGroupStatus(processGroup.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processGroup.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessGroupEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+ }
+
+ @Override
+ public RemoteProcessGroupEntity createRemoteProcessGroup(final Revision revision, final String groupId, final RemoteProcessGroupDTO remoteProcessGroupDTO) {
+ final RevisionUpdate<RemoteProcessGroupDTO> snapshot = createComponent(
+ revision,
+ remoteProcessGroupDTO,
+ () -> remoteProcessGroupDAO.createRemoteProcessGroup(groupId, remoteProcessGroupDTO),
+ remoteProcessGroup -> dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup));
+
+ final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
+ final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroup, controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroup.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroup.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()),
+ permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public boolean isRemoteGroupPortConnected(final String remoteProcessGroupId, final String remotePortId) {
+ final RemoteProcessGroup rpg = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId);
+ RemoteGroupPort port = rpg.getInputPort(remotePortId);
+ if (port != null) {
+ return port.hasIncomingConnection();
+ }
+
+ port = rpg.getOutputPort(remotePortId);
+ if (port != null) {
+ return !port.getConnections().isEmpty();
+ }
+
+ throw new ResourceNotFoundException("Could not find Port with ID " + remotePortId + " as a child of RemoteProcessGroup with ID " + remoteProcessGroupId);
+ }
+
+ @Override
+ public void verifyCanAddTemplate(String groupId, String name) {
+ templateDAO.verifyCanAddTemplate(name, groupId);
+ }
+
+ @Override
+ public void verifyComponentTypes(FlowSnippetDTO snippet) {
+ templateDAO.verifyComponentTypes(snippet);
+ }
+
+ @Override
+ public void verifyComponentTypes(final VersionedProcessGroup versionedGroup) {
+ controllerFacade.verifyComponentTypes(versionedGroup);
+ }
+
+ @Override
+ public void verifyImportProcessGroup(final VersionControlInformationDTO versionControlInfo, final VersionedProcessGroup contents, final String groupId) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ verifyImportProcessGroup(versionControlInfo, contents, group);
+ }
+
+ private void verifyImportProcessGroup(final VersionControlInformationDTO vciDto, final VersionedProcessGroup contents, final ProcessGroup group) {
+ if (group == null) {
+ return;
+ }
+
+ final VersionControlInformation vci = group.getVersionControlInformation();
+ if (vci != null) {
+ // Note that we do not compare the Registry ID here because there could be two registry clients
+ // that point to the same server (one could point to localhost while another points to 127.0.0.1, for instance)..
+ if (Objects.equals(vciDto.getBucketId(), vci.getBucketIdentifier())
+ && Objects.equals(vciDto.getFlowId(), vci.getFlowIdentifier())) {
+
+ throw new IllegalStateException("Cannot import the specified Versioned Flow into the Process Group because doing so would cause a recursive dataflow. "
+ + "If Process Group A contains Process Group B, then Process Group B is not allowed to contain the flow identified by Process Group A.");
+ }
+ }
+
+ final Set<VersionedProcessGroup> childGroups = contents.getProcessGroups();
+ if (childGroups != null) {
+ for (final VersionedProcessGroup childGroup : childGroups) {
+ final VersionedFlowCoordinates childCoordinates = childGroup.getVersionedFlowCoordinates();
+ if (childCoordinates != null) {
+ final VersionControlInformationDTO childVci = new VersionControlInformationDTO();
+ childVci.setBucketId(childCoordinates.getBucketId());
+ childVci.setFlowId(childCoordinates.getFlowId());
+ verifyImportProcessGroup(childVci, childGroup, group);
+ }
+ }
+ }
+
+ verifyImportProcessGroup(vciDto, contents, group.getParent());
+ }
+
+ @Override
+ public TemplateDTO createTemplate(final String name, final String description, final String snippetId, final String groupId, final Optional<String> idGenerationSeed) {
+ // get the specified snippet
+ final Snippet snippet = snippetDAO.getSnippet(snippetId);
+
+ // create the template
+ final TemplateDTO templateDTO = new TemplateDTO();
+ templateDTO.setName(name);
+ templateDTO.setDescription(description);
+ templateDTO.setTimestamp(new Date());
+ templateDTO.setSnippet(snippetUtils.populateFlowSnippet(snippet, true, true, true));
+ templateDTO.setEncodingVersion(TemplateDTO.MAX_ENCODING_VERSION);
+
+ // set the id based on the specified seed
+ final String uuid = idGenerationSeed.isPresent() ? (UUID.nameUUIDFromBytes(idGenerationSeed.get().getBytes(StandardCharsets.UTF_8))).toString() : UUID.randomUUID().toString();
+ templateDTO.setId(uuid);
+
+ // create the template
+ final Template template = templateDAO.createTemplate(templateDTO, groupId);
+
+ // drop the snippet
+ snippetDAO.dropSnippet(snippetId);
+
+ // save the flow
+ controllerFacade.save();
+
+ return dtoFactory.createTemplateDTO(template);
+ }
+
+ /**
+ * Ensures default values are populated for all components in this snippet. This is necessary to handle old templates without default values
+ * and when existing properties have default values introduced.
+ *
+ * @param snippet snippet
+ */
+ private void ensureDefaultPropertyValuesArePopulated(final FlowSnippetDTO snippet) {
+ if (snippet != null) {
+ if (snippet.getControllerServices() != null) {
+ snippet.getControllerServices().forEach(dto -> {
+ if (dto.getProperties() == null) {
+ dto.setProperties(new LinkedHashMap<>());
+ }
+
+ try {
+ final ConfigurableComponent configurableComponent = controllerFacade.getTemporaryComponent(dto.getType(), dto.getBundle());
+ configurableComponent.getPropertyDescriptors().forEach(descriptor -> {
+ if (dto.getProperties().get(descriptor.getName()) == null) {
+ dto.getProperties().put(descriptor.getName(), descriptor.getDefaultValue());
+ }
+ });
+ } catch (final Exception e) {
+ logger.warn(String.format("Unable to create ControllerService of type %s to populate default values.", dto.getType()));
+ }
+ });
+ }
+
+ if (snippet.getProcessors() != null) {
+ snippet.getProcessors().forEach(dto -> {
+ if (dto.getConfig() == null) {
+ dto.setConfig(new ProcessorConfigDTO());
+ }
+
+ final ProcessorConfigDTO config = dto.getConfig();
+ if (config.getProperties() == null) {
+ config.setProperties(new LinkedHashMap<>());
+ }
+
+ try {
+ final ConfigurableComponent configurableComponent = controllerFacade.getTemporaryComponent(dto.getType(), dto.getBundle());
+ configurableComponent.getPropertyDescriptors().forEach(descriptor -> {
+ if (config.getProperties().get(descriptor.getName()) == null) {
+ config.getProperties().put(descriptor.getName(), descriptor.getDefaultValue());
+ }
+ });
+ } catch (final Exception e) {
+ logger.warn(String.format("Unable to create Processor of type %s to populate default values.", dto.getType()));
+ }
+ });
+ }
+
+ if (snippet.getProcessGroups() != null) {
+ snippet.getProcessGroups().forEach(processGroup -> {
+ ensureDefaultPropertyValuesArePopulated(processGroup.getContents());
+ });
+ }
+ }
+ }
+
+ @Override
+ public TemplateDTO importTemplate(final TemplateDTO templateDTO, final String groupId, final Optional<String> idGenerationSeed) {
+ // ensure id is set
+ final String uuid = idGenerationSeed.isPresent() ? (UUID.nameUUIDFromBytes(idGenerationSeed.get().getBytes(StandardCharsets.UTF_8))).toString() : UUID.randomUUID().toString();
+ templateDTO.setId(uuid);
+
+ // mark the timestamp
+ templateDTO.setTimestamp(new Date());
+
+ // ensure default values are populated
+ ensureDefaultPropertyValuesArePopulated(templateDTO.getSnippet());
+
+ // import the template
+ final Template template = templateDAO.importTemplate(templateDTO, groupId);
+
+ // save the flow
+ controllerFacade.save();
+
+ // return the template dto
+ return dtoFactory.createTemplateDTO(template);
+ }
+
+ /**
+ * Post processes a new flow snippet including validation, removing the snippet, and DTO conversion.
+ *
+ * @param groupId group id
+ * @param snippet snippet
+ * @return flow dto
+ */
+ private FlowDTO postProcessNewFlowSnippet(final String groupId, final FlowSnippetDTO snippet) {
+ // validate the new snippet
+ validateSnippetContents(snippet);
+
+ // identify all components added
+ final Set<String> identifiers = new HashSet<>();
+ snippet.getProcessors().stream()
+ .map(proc -> proc.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getConnections().stream()
+ .map(conn -> conn.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getInputPorts().stream()
+ .map(port -> port.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getOutputPorts().stream()
+ .map(port -> port.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getProcessGroups().stream()
+ .map(group -> group.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getRemoteProcessGroups().stream()
+ .map(remoteGroup -> remoteGroup.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getRemoteProcessGroups().stream()
+ .filter(remoteGroup -> remoteGroup.getContents() != null && remoteGroup.getContents().getInputPorts() != null)
+ .flatMap(remoteGroup -> remoteGroup.getContents().getInputPorts().stream())
+ .map(remoteInputPort -> remoteInputPort.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getRemoteProcessGroups().stream()
+ .filter(remoteGroup -> remoteGroup.getContents() != null && remoteGroup.getContents().getOutputPorts() != null)
+ .flatMap(remoteGroup -> remoteGroup.getContents().getOutputPorts().stream())
+ .map(remoteOutputPort -> remoteOutputPort.getId())
+ .forEach(id -> identifiers.add(id));
+ snippet.getLabels().stream()
+ .map(label -> label.getId())
+ .forEach(id -> identifiers.add(id));
+
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ final ProcessGroupStatus groupStatus = controllerFacade.getProcessGroupStatus(groupId);
+ return dtoFactory.createFlowDto(group, groupStatus, snippet, revisionManager, this::getProcessGroupBulletins);
+ }
+
+ @Override
+ public FlowEntity createTemplateInstance(final String groupId, final Double originX, final Double originY, final String templateEncodingVersion,
+ final FlowSnippetDTO requestSnippet, final String idGenerationSeed) {
+
+ // instantiate the template - there is no need to make another copy of the flow snippet since the actual template
+ // was copied and this dto is only used to instantiate it's components (which as already completed)
+ final FlowSnippetDTO snippet = templateDAO.instantiateTemplate(groupId, originX, originY, templateEncodingVersion, requestSnippet, idGenerationSeed);
+
+ // save the flow
+ controllerFacade.save();
+
+ // post process the new flow snippet
+ final FlowDTO flowDto = postProcessNewFlowSnippet(groupId, snippet);
+
+ final FlowEntity flowEntity = new FlowEntity();
+ flowEntity.setFlow(flowDto);
+ return flowEntity;
+ }
+
+ @Override
+ public ControllerServiceEntity createControllerService(final Revision revision, final String groupId, final ControllerServiceDTO controllerServiceDTO) {
+ controllerServiceDTO.setParentGroupId(groupId);
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request claim for component to be created... revision already verified (version == 0)
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ControllerServiceDTO> snapshot;
+ if (groupId == null) {
+ // update revision through revision manager
+ snapshot = revisionManager.updateRevision(claim, user, () -> {
+ // Unfortunately, we can not use the createComponent() method here because createComponent() wants to obtain the read lock
+ // on the group. The Controller Service may or may not have a Process Group (it won't if it's controller-scoped).
+ final ControllerServiceNode controllerService = controllerServiceDAO.createControllerService(controllerServiceDTO);
+ controllerFacade.save();
+
+ awaitValidationCompletion(controllerService);
+ final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(controllerService);
+
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+ } else {
+ snapshot = revisionManager.updateRevision(claim, user, () -> {
+ final ControllerServiceNode controllerService = controllerServiceDAO.createControllerService(controllerServiceDTO);
+ controllerFacade.save();
+
+ awaitValidationCompletion(controllerService);
+ final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(controllerService);
+
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+ }
+
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
+ }
+
+ @Override
+ public ControllerServiceEntity updateControllerService(final Revision revision, final ControllerServiceDTO controllerServiceDTO) {
+ // get the component, ensure we have access to it, and perform the update request
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+ final RevisionUpdate<ControllerServiceDTO> snapshot = updateComponent(revision,
+ controllerService,
+ () -> controllerServiceDAO.updateControllerService(controllerServiceDTO),
+ cs -> {
+ awaitValidationCompletion(cs);
+ final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(cs);
+ final ControllerServiceReference ref = controllerService.getReferences();
+ final ControllerServiceReferencingComponentsEntity referencingComponentsEntity =
+ createControllerServiceReferencingComponentsEntity(ref, Sets.newHashSet(controllerService.getIdentifier()));
+ dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+ return dto;
+ });
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
+ }
+
+
+ @Override
+ public ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingComponents(
+ final Map<String, Revision> referenceRevisions, final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) {
+
+ final RevisionClaim claim = new StandardRevisionClaim(referenceRevisions.values());
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionUpdate<ControllerServiceReferencingComponentsEntity> update = revisionManager.updateRevision(claim, user,
+ new UpdateRevisionTask<ControllerServiceReferencingComponentsEntity>() {
+ @Override
+ public RevisionUpdate<ControllerServiceReferencingComponentsEntity> update() {
+ final Set<ComponentNode> updated = controllerServiceDAO.updateControllerServiceReferencingComponents(controllerServiceId, scheduledState, controllerServiceState);
+ final ControllerServiceReference updatedReference = controllerServiceDAO.getControllerService(controllerServiceId).getReferences();
+
+ // get the revisions of the updated components
+ final Map<String, Revision> updatedRevisions = new HashMap<>();
+ for (final ComponentNode component : updated) {
+ final Revision currentRevision = revisionManager.getRevision(component.getIdentifier());
+ final Revision requestRevision = referenceRevisions.get(component.getIdentifier());
+ updatedRevisions.put(component.getIdentifier(), currentRevision.incrementRevision(requestRevision.getClientId()));
+ }
+
+ // ensure the revision for all referencing components is included regardless of whether they were updated in this request
+ for (final ComponentNode component : updatedReference.findRecursiveReferences(ComponentNode.class)) {
+ updatedRevisions.putIfAbsent(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
+ }
+
+ final ControllerServiceReferencingComponentsEntity entity = createControllerServiceReferencingComponentsEntity(updatedReference, updatedRevisions);
+ return new StandardRevisionUpdate<>(entity, null, new HashSet<>(updatedRevisions.values()));
+ }
+ });
+
+ return update.getComponent();
+ }
+
+ /**
+ * Finds the identifiers for all components referencing a ControllerService.
+ *
+ * @param reference ControllerServiceReference
+ * @param visited ControllerServices we've already visited
+ */
+ private void findControllerServiceReferencingComponentIdentifiers(final ControllerServiceReference reference, final Set<ControllerServiceNode> visited) {
+ for (final ComponentNode component : reference.getReferencingComponents()) {
+
+ // if this is a ControllerService consider it's referencing components
+ if (component instanceof ControllerServiceNode) {
+ final ControllerServiceNode node = (ControllerServiceNode) component;
+ if (!visited.contains(node)) {
+ visited.add(node);
+ findControllerServiceReferencingComponentIdentifiers(node.getReferences(), visited);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates entities for components referencing a ControllerService using their current revision.
+ *
+ * @param reference ControllerServiceReference
+ * @return The entity
+ */
+ private ControllerServiceReferencingComponentsEntity createControllerServiceReferencingComponentsEntity(final ControllerServiceReference reference, final Set<String> lockedIds) {
+ final Set<ControllerServiceNode> visited = new HashSet<>();
+ visited.add(reference.getReferencedComponent());
+ findControllerServiceReferencingComponentIdentifiers(reference, visited);
+
+ final Map<String, Revision> referencingRevisions = new HashMap<>();
+ for (final ComponentNode component : reference.getReferencingComponents()) {
+ referencingRevisions.put(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
+ }
+
+ return createControllerServiceReferencingComponentsEntity(reference, referencingRevisions);
+ }
+
+ /**
+ * Creates entities for components referencing a ControllerService using the specified revisions.
+ *
+ * @param reference ControllerServiceReference
+ * @param revisions The revisions
+ * @return The entity
+ */
+ private ControllerServiceReferencingComponentsEntity createControllerServiceReferencingComponentsEntity(
+ final ControllerServiceReference reference, final Map<String, Revision> revisions) {
+ final Set<ControllerServiceNode> visited = new HashSet<>();
+ visited.add(reference.getReferencedComponent());
+ return createControllerServiceReferencingComponentsEntity(reference, revisions, visited);
+ }
+
+ /**
+ * Creates entities for components referencing a ControllerServcie using the specified revisions.
+ *
+ * @param reference ControllerServiceReference
+ * @param revisions The revisions
+ * @param visited Which services we've already considered (in case of cycle)
+ * @return The entity
+ */
+ private ControllerServiceReferencingComponentsEntity createControllerServiceReferencingComponentsEntity(
+ final ControllerServiceReference reference, final Map<String, Revision> revisions, final Set<ControllerServiceNode> visited) {
+
+ final String modifier = NiFiUserUtils.getNiFiUserIdentity();
+ final Set<ComponentNode> referencingComponents = reference.getReferencingComponents();
+
+ final Set<ControllerServiceReferencingComponentEntity> componentEntities = new HashSet<>();
+ for (final ComponentNode refComponent : referencingComponents) {
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(refComponent);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(refComponent));
+
+ final Revision revision = revisions.get(refComponent.getIdentifier());
+ final FlowModification flowMod = new FlowModification(revision, modifier);
+ final RevisionDTO revisionDto = dtoFactory.createRevisionDTO(flowMod);
+ final ControllerServiceReferencingComponentDTO dto = dtoFactory.createControllerServiceReferencingComponentDTO(refComponent);
+
+ if (refComponent instanceof ControllerServiceNode) {
+ final ControllerServiceNode node = (ControllerServiceNode) refComponent;
+
+ // indicate if we've hit a cycle
+ dto.setReferenceCycle(visited.contains(node));
+
+ // mark node as visited before building the reference cycle
+ visited.add(node);
+
+ // if we haven't encountered this service before include it's referencing components
+ if (!dto.getReferenceCycle()) {
+ final ControllerServiceReference refReferences = node.getReferences();
+ final Map<String, Revision> referencingRevisions = new HashMap<>(revisions);
+ for (final ComponentNode component : refReferences.getReferencingComponents()) {
+ referencingRevisions.putIfAbsent(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
+ }
+ final ControllerServiceReferencingComponentsEntity references = createControllerServiceReferencingComponentsEntity(refReferences, referencingRevisions, visited);
+ dto.setReferencingComponents(references.getControllerServiceReferencingComponents());
+ }
+ }
+
+ componentEntities.add(entityFactory.createControllerServiceReferencingComponentEntity(refComponent.getIdentifier(), dto, revisionDto, permissions, operatePermissions));
+ }
+
+ final ControllerServiceReferencingComponentsEntity entity = new ControllerServiceReferencingComponentsEntity();
+ entity.setControllerServiceReferencingComponents(componentEntities);
+ return entity;
+ }
+
+ @Override
+ public ControllerServiceEntity deleteControllerService(final Revision revision, final String controllerServiceId) {
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+ final ControllerServiceDTO snapshot = deleteComponent(
+ revision,
+ controllerService.getResource(),
+ () -> controllerServiceDAO.deleteControllerService(controllerServiceId),
+ true,
+ dtoFactory.createControllerServiceDto(controllerService));
+
+ return entityFactory.createControllerServiceEntity(snapshot, null, permissions, operatePermissions, null);
+ }
+
+
+ @Override
+ public RegistryClientEntity createRegistryClient(Revision revision, RegistryDTO registryDTO) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request claim for component to be created... revision already verified (version == 0)
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ // update revision through revision manager
+ final RevisionUpdate<FlowRegistry> revisionUpdate = revisionManager.updateRevision(claim, user, () -> {
+ // add the component
+ final FlowRegistry registry = registryDAO.createFlowRegistry(registryDTO);
+
+ // save the flow
+ controllerFacade.save();
+
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(registry, lastMod);
+ });
+
+ final FlowRegistry registry = revisionUpdate.getComponent();
+ return createRegistryClientEntity(registry);
+ }
+
+ @Override
+ public RegistryClientEntity getRegistryClient(final String registryId) {
+ final FlowRegistry registry = registryDAO.getFlowRegistry(registryId);
+ return createRegistryClientEntity(registry);
+ }
+
+ private RegistryClientEntity createRegistryClientEntity(final FlowRegistry flowRegistry) {
+ if (flowRegistry == null) {
+ return null;
+ }
+
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(flowRegistry.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getController());
+ final RegistryDTO dto = dtoFactory.createRegistryDto(flowRegistry);
+
+ return entityFactory.createRegistryClientEntity(dto, revision, permissions);
+ }
+
+ private VersionedFlowEntity createVersionedFlowEntity(final String registryId, final VersionedFlow versionedFlow) {
+ if (versionedFlow == null) {
+ return null;
+ }
+
+ final VersionedFlowDTO dto = new VersionedFlowDTO();
+ dto.setRegistryId(registryId);
+ dto.setBucketId(versionedFlow.getBucketIdentifier());
+ dto.setFlowId(versionedFlow.getIdentifier());
+ dto.setFlowName(versionedFlow.getName());
+ dto.setDescription(versionedFlow.getDescription());
+
+ final VersionedFlowEntity entity = new VersionedFlowEntity();
+ entity.setVersionedFlow(dto);
+
+ return entity;
+ }
+
+ private VersionedFlowSnapshotMetadataEntity createVersionedFlowSnapshotMetadataEntity(final String registryId, final VersionedFlowSnapshotMetadata metadata) {
+ if (metadata == null) {
+ return null;
+ }
+
+ final VersionedFlowSnapshotMetadataEntity entity = new VersionedFlowSnapshotMetadataEntity();
+ entity.setRegistryId(registryId);
+ entity.setVersionedFlowMetadata(metadata);
+
+ return entity;
+ }
+
+ @Override
+ public Set<RegistryClientEntity> getRegistryClients() {
+ return registryDAO.getFlowRegistries().stream()
+ .map(this::createRegistryClientEntity)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<RegistryEntity> getRegistriesForUser(final NiFiUser user) {
+ return registryDAO.getFlowRegistriesForUser(user).stream()
+ .map(flowRegistry -> entityFactory.createRegistryEntity(dtoFactory.createRegistryDto(flowRegistry)))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<BucketEntity> getBucketsForUser(final String registryId, final NiFiUser user) {
+ return registryDAO.getBucketsForUser(registryId, user).stream()
+ .map(bucket -> {
+ if (bucket == null) {
+ return null;
+ }
+
+ final BucketDTO dto = new BucketDTO();
+ dto.setId(bucket.getIdentifier());
+ dto.setName(bucket.getName());
+ dto.setDescription(bucket.getDescription());
+ dto.setCreated(bucket.getCreatedTimestamp());
+
+ final Permissions regPermissions = bucket.getPermissions();
+ final PermissionsDTO permissions = new PermissionsDTO();
+ permissions.setCanRead(regPermissions.getCanRead());
+ permissions.setCanWrite(regPermissions.getCanWrite());
+
+ return entityFactory.createBucketEntity(dto, permissions);
+ })
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<VersionedFlowEntity> getFlowsForUser(String registryId, String bucketId, NiFiUser user) {
+ return registryDAO.getFlowsForUser(registryId, bucketId, user).stream()
+ .map(vf -> createVersionedFlowEntity(registryId, vf))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<VersionedFlowSnapshotMetadataEntity> getFlowVersionsForUser(String registryId, String bucketId, String flowId, NiFiUser user) {
+ return registryDAO.getFlowVersionsForUser(registryId, bucketId, flowId, user).stream()
+ .map(md -> createVersionedFlowSnapshotMetadataEntity(registryId, md))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public RegistryClientEntity updateRegistryClient(Revision revision, RegistryDTO registryDTO) {
+ final RevisionClaim revisionClaim = new StandardRevisionClaim(revision);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final FlowRegistry registry = registryDAO.getFlowRegistry(registryDTO.getId());
+ final RevisionUpdate<FlowRegistry> revisionUpdate = revisionManager.updateRevision(revisionClaim, user, () -> {
+ final boolean duplicateName = registryDAO.getFlowRegistries().stream()
+ .anyMatch(reg -> reg.getName().equals(registryDTO.getName()) && !reg.getIdentifier().equals(registryDTO.getId()));
+
+ if (duplicateName) {
+ throw new IllegalStateException("Cannot update Flow Registry because a Flow Registry already exists with the name " + registryDTO.getName());
+ }
+
+ registry.setDescription(registryDTO.getDescription());
+ registry.setName(registryDTO.getName());
+ registry.setURL(registryDTO.getUri());
+
+ controllerFacade.save();
+
+ final Revision updatedRevision = revisionManager.getRevision(revision.getComponentId()).incrementRevision(revision.getClientId());
+ final FlowModification lastModification = new FlowModification(updatedRevision, user.getIdentity());
+
+ return new StandardRevisionUpdate<>(registry, lastModification);
+ });
+
+ final FlowRegistry updatedReg = revisionUpdate.getComponent();
+ return createRegistryClientEntity(updatedReg);
+ }
+
+ @Override
+ public void verifyDeleteRegistry(String registryId) {
+ processGroupDAO.verifyDeleteFlowRegistry(registryId);
+ }
+
+ @Override
+ public RegistryClientEntity deleteRegistryClient(final Revision revision, final String registryId) {
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final FlowRegistry registry = revisionManager.deleteRevision(claim, user, () -> {
+ final FlowRegistry reg = registryDAO.removeFlowRegistry(registryId);
+ controllerFacade.save();
+ return reg;
+ });
+
+ return createRegistryClientEntity(registry);
+ }
+
+ @Override
+ public ReportingTaskEntity createReportingTask(final Revision revision, final ReportingTaskDTO reportingTaskDTO) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request claim for component to be created... revision already verified (version == 0)
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ // update revision through revision manager
+ final RevisionUpdate<ReportingTaskDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ // create the reporting task
+ final ReportingTaskNode reportingTask = reportingTaskDAO.createReportingTask(reportingTaskDTO);
+
+ // save the update
+ controllerFacade.save();
+ awaitValidationCompletion(reportingTask);
+
+ final ReportingTaskDTO dto = dtoFactory.createReportingTaskDto(reportingTask);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskDTO.getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
+ }
+
+ @Override
+ public ReportingTaskEntity updateReportingTask(final Revision revision, final ReportingTaskDTO reportingTaskDTO) {
+ // get the component, ensure we have access to it, and perform the update request
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskDTO.getId());
+ final RevisionUpdate<ReportingTaskDTO> snapshot = updateComponent(revision,
+ reportingTask,
+ () -> reportingTaskDAO.updateReportingTask(reportingTaskDTO),
+ rt -> {
+ awaitValidationCompletion(rt);
+ return dtoFactory.createReportingTaskDto(rt);
+ });
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
+ }
+
+ @Override
+ public ReportingTaskEntity deleteReportingTask(final Revision revision, final String reportingTaskId) {
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
+ final ReportingTaskDTO snapshot = deleteComponent(
+ revision,
+ reportingTask.getResource(),
+ () -> reportingTaskDAO.deleteReportingTask(reportingTaskId),
+ true,
+ dtoFactory.createReportingTaskDto(reportingTask));
+
+ return entityFactory.createReportingTaskEntity(snapshot, null, permissions, operatePermissions, null);
+ }
+
+ @Override
+ public void deleteActions(final Date endDate) {
+ // get the user from the request
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new WebApplicationException(new Throwable("Unable to access details for current user."));
+ }
+
+ // create the purge details
+ final FlowChangePurgeDetails details = new FlowChangePurgeDetails();
+ details.setEndDate(endDate);
+
+ // create a purge action to record that records are being removed
+ final FlowChangeAction purgeAction = new FlowChangeAction();
+ purgeAction.setUserIdentity(user.getIdentity());
+ purgeAction.setOperation(Operation.Purge);
+ purgeAction.setTimestamp(new Date());
+ purgeAction.setSourceId("Flow Controller");
+ purgeAction.setSourceName("History");
+ purgeAction.setSourceType(Component.Controller);
+ purgeAction.setActionDetails(details);
+
+ // purge corresponding actions
+ auditService.purgeActions(endDate, purgeAction);
+ }
+
+ @Override
+ public ProvenanceDTO submitProvenance(final ProvenanceDTO query) {
+ return controllerFacade.submitProvenance(query);
+ }
+
+ @Override
+ public void deleteProvenance(final String queryId) {
+ controllerFacade.deleteProvenanceQuery(queryId);
+ }
+
+ @Override
+ public LineageDTO submitLineage(final LineageDTO lineage) {
+ return controllerFacade.submitLineage(lineage);
+ }
+
+ @Override
+ public void deleteLineage(final String lineageId) {
+ controllerFacade.deleteLineage(lineageId);
+ }
+
+ @Override
+ public ProvenanceEventDTO submitReplay(final Long eventId) {
+ return controllerFacade.submitReplay(eventId);
+ }
+
+ // -----------------------------------------
+ // Read Operations
+ // -----------------------------------------
+
+ @Override
+ public SearchResultsDTO searchController(final String query) {
+ return controllerFacade.search(query);
+ }
+
+ @Override
+ public DownloadableContent getContent(final String connectionId, final String flowFileUuid, final String uri) {
+ return connectionDAO.getContent(connectionId, flowFileUuid, uri);
+ }
+
+ @Override
+ public DownloadableContent getContent(final Long eventId, final String uri, final ContentDirection contentDirection) {
+ return controllerFacade.getContent(eventId, uri, contentDirection);
+ }
+
+ @Override
+ public ProvenanceDTO getProvenance(final String queryId, final Boolean summarize, final Boolean incrementalResults) {
+ return controllerFacade.getProvenanceQuery(queryId, summarize, incrementalResults);
+ }
+
+ @Override
+ public LineageDTO getLineage(final String lineageId) {
+ return controllerFacade.getLineage(lineageId);
+ }
+
+ @Override
+ public ProvenanceOptionsDTO getProvenanceSearchOptions() {
+ return controllerFacade.getProvenanceSearchOptions();
+ }
+
+ @Override
+ public ProvenanceEventDTO getProvenanceEvent(final Long id) {
+ return controllerFacade.getProvenanceEvent(id);
+ }
+
+ @Override
+ public ProcessGroupStatusEntity getProcessGroupStatus(final String groupId, final boolean recursive) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ final ProcessGroupStatusDTO dto = dtoFactory.createProcessGroupStatusDto(processGroup, controllerFacade.getProcessGroupStatus(groupId));
+
+ // prune the response as necessary
+ if (!recursive) {
+ pruneChildGroups(dto.getAggregateSnapshot());
+ if (dto.getNodeSnapshots() != null) {
+ for (final NodeProcessGroupStatusSnapshotDTO nodeSnapshot : dto.getNodeSnapshots()) {
+ pruneChildGroups(nodeSnapshot.getStatusSnapshot());
+ }
+ }
+ }
+
+ return entityFactory.createProcessGroupStatusEntity(dto, permissions);
+ }
+
+ private void pruneChildGroups(final ProcessGroupStatusSnapshotDTO snapshot) {
+ for (final ProcessGroupStatusSnapshotEntity childProcessGroupStatusEntity : snapshot.getProcessGroupStatusSnapshots()) {
+ final ProcessGroupStatusSnapshotDTO childProcessGroupStatus = childProcessGroupStatusEntity.getProcessGroupStatusSnapshot();
+ childProcessGroupStatus.setConnectionStatusSnapshots(null);
+ childProcessGroupStatus.setProcessGroupStatusSnapshots(null);
+ childProcessGroupStatus.setInputPortStatusSnapshots(null);
+ childProcessGroupStatus.setOutputPortStatusSnapshots(null);
+ childProcessGroupStatus.setProcessorStatusSnapshots(null);
+ childProcessGroupStatus.setRemoteProcessGroupStatusSnapshots(null);
+ }
+ }
+
+ @Override
+ public ControllerStatusDTO getControllerStatus() {
+ return controllerFacade.getControllerStatus();
+ }
+
+ @Override
+ public ComponentStateDTO getProcessorState(final String processorId) {
+ final StateMap clusterState = isClustered() ? processorDAO.getState(processorId, Scope.CLUSTER) : null;
+ final StateMap localState = processorDAO.getState(processorId, Scope.LOCAL);
+
+ // processor will be non null as it was already found when getting the state
+ final ProcessorNode processor = processorDAO.getProcessor(processorId);
+ return dtoFactory.createComponentStateDTO(processorId, processor.getProcessor().getClass(), localState, clusterState);
+ }
+
+ @Override
+ public ComponentStateDTO getControllerServiceState(final String controllerServiceId) {
+ final StateMap clusterState = isClustered() ? controllerServiceDAO.getState(controllerServiceId, Scope.CLUSTER) : null;
+ final StateMap localState = controllerServiceDAO.getState(controllerServiceId, Scope.LOCAL);
+
+ // controller service will be non null as it was already found when getting the state
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId);
+ return dtoFactory.createComponentStateDTO(controllerServiceId, controllerService.getControllerServiceImplementation().getClass(), localState, clusterState);
+ }
+
+ @Override
+ public ComponentStateDTO getReportingTaskState(final String reportingTaskId) {
+ final StateMap clusterState = isClustered() ? reportingTaskDAO.getState(reportingTaskId, Scope.CLUSTER) : null;
+ final StateMap localState = reportingTaskDAO.getState(reportingTaskId, Scope.LOCAL);
+
+ // reporting task will be non null as it was already found when getting the state
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId);
+ return dtoFactory.createComponentStateDTO(reportingTaskId, reportingTask.getReportingTask().getClass(), localState, clusterState);
+ }
+
+ @Override
+ public CountersDTO getCounters() {
+ final List<Counter> counters = controllerFacade.getCounters();
+ final Set<CounterDTO> counterDTOs = new LinkedHashSet<>(counters.size());
+ for (final Counter counter : counters) {
+ counterDTOs.add(dtoFactory.createCounterDto(counter));
+ }
+
+ final CountersSnapshotDTO snapshotDto = dtoFactory.createCountersDto(counterDTOs);
+ final CountersDTO countersDto = new CountersDTO();
+ countersDto.setAggregateSnapshot(snapshotDto);
+
+ return countersDto;
+ }
+
+ private ConnectionEntity createConnectionEntity(final Connection connection) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(connection.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connection);
+ final ConnectionStatusDTO status = dtoFactory.createConnectionStatusDto(controllerFacade.getConnectionStatus(connection.getIdentifier()));
+ return entityFactory.createConnectionEntity(dtoFactory.createConnectionDto(connection), revision, permissions, status);
+ }
+
+ @Override
+ public Set<ConnectionEntity> getConnections(final String groupId) {
+ final Set<Connection> connections = connectionDAO.getConnections(groupId);
+ return connections.stream()
+ .map(connection -> createConnectionEntity(connection))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ConnectionEntity getConnection(final String connectionId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ return createConnectionEntity(connection);
+ }
+
+ @Override
+ public DropRequestDTO getFlowFileDropRequest(final String connectionId, final String dropRequestId) {
+ return dtoFactory.createDropRequestDTO(connectionDAO.getFlowFileDropRequest(connectionId, dropRequestId));
+ }
+
+ @Override
+ public ListingRequestDTO getFlowFileListingRequest(final String connectionId, final String listingRequestId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final ListingRequestDTO listRequest = dtoFactory.createListingRequestDTO(connectionDAO.getFlowFileListingRequest(connectionId, listingRequestId));
+
+ // include whether the source and destination are running
+ if (connection.getSource() != null) {
+ listRequest.setSourceRunning(connection.getSource().isRunning());
+ }
+ if (connection.getDestination() != null) {
+ listRequest.setDestinationRunning(connection.getDestination().isRunning());
+ }
+
+ return listRequest;
+ }
+
+ @Override
+ public FlowFileDTO getFlowFile(final String connectionId, final String flowFileUuid) {
+ return dtoFactory.createFlowFileDTO(connectionDAO.getFlowFile(connectionId, flowFileUuid));
+ }
+
+ @Override
+ public ConnectionStatusEntity getConnectionStatus(final String connectionId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connection);
+ final ConnectionStatusDTO dto = dtoFactory.createConnectionStatusDto(controllerFacade.getConnectionStatus(connectionId));
+ return entityFactory.createConnectionStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public StatusHistoryEntity getConnectionStatusHistory(final String connectionId) {
+ final Connection connection = connectionDAO.getConnection(connectionId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connection);
+ final StatusHistoryDTO dto = controllerFacade.getConnectionStatusHistory(connectionId);
+ return entityFactory.createStatusHistoryEntity(dto, permissions);
+ }
+
+ private ProcessorEntity createProcessorEntity(final ProcessorNode processor, final NiFiUser user) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(processor.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor, user);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
+ final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processor.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processor.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessorEntity(dtoFactory.createProcessorDto(processor), revision, permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public Set<ProcessorEntity> getProcessors(final String groupId, final boolean includeDescendants) {
+ final Set<ProcessorNode> processors = processorDAO.getProcessors(groupId, includeDescendants);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ return processors.stream()
+ .map(processor -> createProcessorEntity(processor, user))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public TemplateDTO exportTemplate(final String id) {
+ final Template template = templateDAO.getTemplate(id);
+ final TemplateDTO templateDetails = template.getDetails();
+
+ final TemplateDTO templateDTO = dtoFactory.createTemplateDTO(template);
+ templateDTO.setSnippet(dtoFactory.copySnippetContents(templateDetails.getSnippet()));
+ return templateDTO;
+ }
+
+ @Override
+ public TemplateDTO getTemplate(final String id) {
+ return dtoFactory.createTemplateDTO(templateDAO.getTemplate(id));
+ }
+
+ @Override
+ public Set<TemplateEntity> getTemplates() {
+ return templateDAO.getTemplates().stream()
+ .map(template -> {
+ final TemplateDTO dto = dtoFactory.createTemplateDTO(template);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(template);
+
+ final TemplateEntity entity = new TemplateEntity();
+ entity.setId(dto.getId());
+ entity.setPermissions(permissions);
+ entity.setTemplate(dto);
+ return entity;
+ }).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<DocumentedTypeDTO> getWorkQueuePrioritizerTypes() {
+ return controllerFacade.getFlowFileComparatorTypes();
+ }
+
+ @Override
+ public Set<DocumentedTypeDTO> getProcessorTypes(final String bundleGroup, final String bundleArtifact, final String type) {
+ return controllerFacade.getFlowFileProcessorTypes(bundleGroup, bundleArtifact, type);
+ }
+
+ @Override
+ public Set<DocumentedTypeDTO> getControllerServiceTypes(final String serviceType, final String serviceBundleGroup, final String serviceBundleArtifact, final String serviceBundleVersion,
+ final String bundleGroup, final String bundleArtifact, final String type) {
+ return controllerFacade.getControllerServiceTypes(serviceType, serviceBundleGroup, serviceBundleArtifact, serviceBundleVersion, bundleGroup, bundleArtifact, type);
+ }
+
+ @Override
+ public Set<DocumentedTypeDTO> getReportingTaskTypes(final String bundleGroup, final String bundleArtifact, final String type) {
+ return controllerFacade.getReportingTaskTypes(bundleGroup, bundleArtifact, type);
+ }
+
+ @Override
+ public ProcessorEntity getProcessor(final String id) {
+ final ProcessorNode processor = processorDAO.getProcessor(id);
+ return createProcessorEntity(processor, NiFiUserUtils.getNiFiUser());
+ }
+
+ @Override
+ public PropertyDescriptorDTO getProcessorPropertyDescriptor(final String id, final String property) {
+ final ProcessorNode processor = processorDAO.getProcessor(id);
+ PropertyDescriptor descriptor = processor.getPropertyDescriptor(property);
+
+ // return an invalid descriptor if the processor doesn't support this property
+ if (descriptor == null) {
+ descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
+ }
+
+ return dtoFactory.createPropertyDescriptorDto(descriptor, processor.getProcessGroup().getIdentifier());
+ }
+
+ @Override
+ public ProcessorStatusEntity getProcessorStatus(final String id) {
+ final ProcessorNode processor = processorDAO.getProcessor(id);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+ final ProcessorStatusDTO dto = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(id));
+ return entityFactory.createProcessorStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public StatusHistoryEntity getProcessorStatusHistory(final String id) {
+ final ProcessorNode processor = processorDAO.getProcessor(id);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+ final StatusHistoryDTO dto = controllerFacade.getProcessorStatusHistory(id);
+ return entityFactory.createStatusHistoryEntity(dto, permissions);
+ }
+
+ private boolean authorizeBulletin(final Bulletin bulletin) {
+ final String sourceId = bulletin.getSourceId();
+ final ComponentType type = bulletin.getSourceType();
+
+ final Authorizable authorizable;
+ try {
+ switch (type) {
+ case PROCESSOR:
+ authorizable = authorizableLookup.getProcessor(sourceId).getAuthorizable();
+ break;
+ case REPORTING_TASK:
+ authorizable = authorizableLookup.getReportingTask(sourceId).getAuthorizable();
+ break;
+ case CONTROLLER_SERVICE:
+ authorizable = authorizableLookup.getControllerService(sourceId).getAuthorizable();
+ break;
+ case FLOW_CONTROLLER:
+ authorizable = controllerFacade;
+ break;
+ case INPUT_PORT:
+ authorizable = authorizableLookup.getInputPort(sourceId);
+ break;
+ case OUTPUT_PORT:
+ authorizable = authorizableLookup.getOutputPort(sourceId);
+ break;
+ case REMOTE_PROCESS_GROUP:
+ authorizable = authorizableLookup.getRemoteProcessGroup(sourceId);
+ break;
+ default:
+ throw new WebApplicationException(Response.serverError().entity("An unexpected type of component is the source of this bulletin.").build());
+ }
+ } catch (final ResourceNotFoundException e) {
+ // if the underlying component is gone, disallow
+ return false;
+ }
+
+ // perform the authorization
+ final AuthorizationResult result = authorizable.checkAuthorization(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ return Result.Approved.equals(result.getResult());
+ }
+
+ @Override
+ public BulletinBoardDTO getBulletinBoard(final BulletinQueryDTO query) {
+ // build the query
+ final BulletinQuery.Builder queryBuilder = new BulletinQuery.Builder()
+ .groupIdMatches(query.getGroupId())
+ .sourceIdMatches(query.getSourceId())
+ .nameMatches(query.getName())
+ .messageMatches(query.getMessage())
+ .after(query.getAfter())
+ .limit(query.getLimit());
+
+ // perform the query
+ final List<Bulletin> results = bulletinRepository.findBulletins(queryBuilder.build());
+
+ // perform the query and generate the results - iterating in reverse order since we are
+ // getting the most recent results by ordering by timestamp desc above. this gets the
+ // exact results we want but in reverse order
+ final List<BulletinEntity> bulletinEntities = new ArrayList<>();
+ for (final ListIterator<Bulletin> bulletinIter = results.listIterator(results.size()); bulletinIter.hasPrevious(); ) {
+ final Bulletin bulletin = bulletinIter.previous();
+ bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin), authorizeBulletin(bulletin)));
+ }
+
+ // create the bulletin board
+ final BulletinBoardDTO bulletinBoard = new BulletinBoardDTO();
+ bulletinBoard.setBulletins(bulletinEntities);
+ bulletinBoard.setGenerated(new Date());
+ return bulletinBoard;
+ }
+
+ @Override
+ public SystemDiagnosticsDTO getSystemDiagnostics() {
+ final SystemDiagnostics sysDiagnostics = controllerFacade.getSystemDiagnostics();
+ return dtoFactory.createSystemDiagnosticsDto(sysDiagnostics);
+ }
+
+ @Override
+ public List<ResourceDTO> getResources() {
+ final List<Resource> resources = controllerFacade.getResources();
+ final List<ResourceDTO> resourceDtos = new ArrayList<>(resources.size());
+ for (final Resource resource : resources) {
+ resourceDtos.add(dtoFactory.createResourceDto(resource));
+ }
+ return resourceDtos;
+ }
+
+ @Override
+ public void discoverCompatibleBundles(VersionedProcessGroup versionedGroup) {
+ BundleUtils.discoverCompatibleBundles(controllerFacade.getExtensionManager(), versionedGroup);
+ }
+
+ @Override
+ public BundleCoordinate getCompatibleBundle(String type, BundleDTO bundleDTO) {
+ return BundleUtils.getCompatibleBundle(controllerFacade.getExtensionManager(), type, bundleDTO);
+ }
+
+ @Override
+ public ConfigurableComponent getTempComponent(String classType, BundleCoordinate bundleCoordinate) {
+ return controllerFacade.getExtensionManager().getTempComponent(classType, bundleCoordinate);
+ }
+
+ /**
+ * Ensures the specified user has permission to access the specified port. This method does
+ * not utilize the DataTransferAuthorizable as that will enforce the entire chain is
+ * authorized for the transfer. This method is only invoked when obtaining the site to site
+ * details so the entire chain isn't necessary.
+ */
+ private boolean isUserAuthorized(final NiFiUser user, final RootGroupPort port) {
+ final boolean isSiteToSiteSecure = Boolean.TRUE.equals(properties.isSiteToSiteSecure());
+
+ // if site to site is not secure, allow all users
+ if (!isSiteToSiteSecure) {
+ return true;
+ }
+
+ final Map<String, String> userContext;
+ if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
+ userContext = new HashMap<>();
+ userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+ } else {
+ userContext = null;
+ }
+
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(ResourceFactory.getDataTransferResource(port.getResource()))
+ .identity(user.getIdentity())
+ .groups(user.getGroups())
+ .anonymous(user.isAnonymous())
+ .accessAttempt(false)
+ .action(RequestAction.WRITE)
+ .userContext(userContext)
+ .explanationSupplier(() -> "Unable to retrieve port details.")
+ .build();
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ return Result.Approved.equals(result.getResult());
+ }
+
+ @Override
+ public ControllerDTO getSiteToSiteDetails() {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new WebApplicationException(new Throwable("Unable to access details for current user."));
+ }
+
+ // serialize the input ports this NiFi has access to
+ final Set<PortDTO> inputPortDtos = new LinkedHashSet<>();
+ final Set<RootGroupPort> inputPorts = controllerFacade.getInputPorts();
+ for (final RootGroupPort inputPort : inputPorts) {
+ if (isUserAuthorized(user, inputPort)) {
+ final PortDTO dto = new PortDTO();
+ dto.setId(inputPort.getIdentifier());
+ dto.setName(inputPort.getName());
+ dto.setComments(inputPort.getComments());
+ dto.setState(inputPort.getScheduledState().toString());
+ inputPortDtos.add(dto);
+ }
+ }
+
+ // serialize the output ports this NiFi has access to
+ final Set<PortDTO> outputPortDtos = new LinkedHashSet<>();
+ for (final RootGroupPort outputPort : controllerFacade.getOutputPorts()) {
+ if (isUserAuthorized(user, outputPort)) {
+ final PortDTO dto = new PortDTO();
+ dto.setId(outputPort.getIdentifier());
+ dto.setName(outputPort.getName());
+ dto.setComments(outputPort.getComments());
+ dto.setState(outputPort.getScheduledState().toString());
+ outputPortDtos.add(dto);
+ }
+ }
+
+ // get the root group
+ final ProcessGroup rootGroup = processGroupDAO.getProcessGroup(controllerFacade.getRootGroupId());
+ final ProcessGroupCounts counts = rootGroup.getCounts();
+
+ // create the controller dto
+ final ControllerDTO controllerDTO = new ControllerDTO();
+ controllerDTO.setId(controllerFacade.getRootGroupId());
+ controllerDTO.setInstanceId(controllerFacade.getInstanceId());
+ controllerDTO.setName(controllerFacade.getName());
+ controllerDTO.setComments(controllerFacade.getComments());
+ controllerDTO.setInputPorts(inputPortDtos);
+ controllerDTO.setOutputPorts(outputPortDtos);
+ controllerDTO.setInputPortCount(inputPortDtos.size());
+ controllerDTO.setOutputPortCount(outputPortDtos.size());
+ controllerDTO.setRunningCount(counts.getRunningCount());
+ controllerDTO.setStoppedCount(counts.getStoppedCount());
+ controllerDTO.setInvalidCount(counts.getInvalidCount());
+ controllerDTO.setDisabledCount(counts.getDisabledCount());
+
+ // determine the site to site configuration
+ controllerDTO.setRemoteSiteListeningPort(controllerFacade.getRemoteSiteListeningPort());
+ controllerDTO.setRemoteSiteHttpListeningPort(controllerFacade.getRemoteSiteListeningHttpPort());
+ controllerDTO.setSiteToSiteSecure(controllerFacade.isRemoteSiteCommsSecure());
+
+ return controllerDTO;
+ }
+
+ @Override
+ public ControllerConfigurationEntity getControllerConfiguration() {
+ final Revision rev = revisionManager.getRevision(FlowController.class.getSimpleName());
+ final ControllerConfigurationDTO dto = dtoFactory.createControllerConfigurationDto(controllerFacade);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerFacade);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(rev);
+ return entityFactory.createControllerConfigurationEntity(dto, revision, permissions);
+ }
+
+ @Override
+ public ControllerBulletinsEntity getControllerBulletins() {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final ControllerBulletinsEntity controllerBulletinsEntity = new ControllerBulletinsEntity();
+
+ final List<BulletinEntity> controllerBulletinEntities = new ArrayList<>();
+
+ final Authorizable controllerAuthorizable = authorizableLookup.getController();
+ final boolean authorized = controllerAuthorizable.isAuthorized(authorizer, RequestAction.READ, user);
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForController());
+ controllerBulletinEntities.addAll(bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, authorized)).collect(Collectors.toList()));
+
+ // get the controller service bulletins
+ final BulletinQuery controllerServiceQuery = new BulletinQuery.Builder().sourceType(ComponentType.CONTROLLER_SERVICE).build();
+ final List<Bulletin> allControllerServiceBulletins = bulletinRepository.findBulletins(controllerServiceQuery);
+ final List<BulletinEntity> controllerServiceBulletinEntities = new ArrayList<>();
+ for (final Bulletin bulletin : allControllerServiceBulletins) {
+ try {
+ final Authorizable controllerServiceAuthorizable = authorizableLookup.getControllerService(bulletin.getSourceId()).getAuthorizable();
+ final boolean controllerServiceAuthorized = controllerServiceAuthorizable.isAuthorized(authorizer, RequestAction.READ, user);
+
+ final BulletinEntity controllerServiceBulletin = entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin), controllerServiceAuthorized);
+ controllerServiceBulletinEntities.add(controllerServiceBulletin);
+ controllerBulletinEntities.add(controllerServiceBulletin);
+ } catch (final ResourceNotFoundException e) {
+ // controller service missing.. skip
+ }
+ }
+ controllerBulletinsEntity.setControllerServiceBulletins(controllerServiceBulletinEntities);
+
+ // get the reporting task bulletins
+ final BulletinQuery reportingTaskQuery = new BulletinQuery.Builder().sourceType(ComponentType.REPORTING_TASK).build();
+ final List<Bulletin> allReportingTaskBulletins = bulletinRepository.findBulletins(reportingTaskQuery);
+ final List<BulletinEntity> reportingTaskBulletinEntities = new ArrayList<>();
+ for (final Bulletin bulletin : allReportingTaskBulletins) {
+ try {
+ final Authorizable reportingTaskAuthorizable = authorizableLookup.getReportingTask(bulletin.getSourceId()).getAuthorizable();
+ final boolean reportingTaskAuthorizableAuthorized = reportingTaskAuthorizable.isAuthorized(authorizer, RequestAction.READ, user);
+
+ final BulletinEntity reportingTaskBulletin = entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin), reportingTaskAuthorizableAuthorized);
+ reportingTaskBulletinEntities.add(reportingTaskBulletin);
+ controllerBulletinEntities.add(reportingTaskBulletin);
+ } catch (final ResourceNotFoundException e) {
+ // reporting task missing.. skip
+ }
+ }
+ controllerBulletinsEntity.setReportingTaskBulletins(reportingTaskBulletinEntities);
+
+ controllerBulletinsEntity.setBulletins(pruneAndSortBulletins(controllerBulletinEntities, BulletinRepository.MAX_BULLETINS_FOR_CONTROLLER));
+ return controllerBulletinsEntity;
+ }
+
+ @Override
+ public FlowConfigurationEntity getFlowConfiguration() {
+ final FlowConfigurationDTO dto = dtoFactory.createFlowConfigurationDto(properties.getAutoRefreshInterval(),
+ properties.getDefaultBackPressureObjectThreshold(), properties.getDefaultBackPressureDataSizeThreshold(),properties.getDcaeDistributorApiHostname());
+ final FlowConfigurationEntity entity = new FlowConfigurationEntity();
+ entity.setFlowConfiguration(dto);
+ return entity;
+ }
+
+ @Override
+ public AccessPolicyEntity getAccessPolicy(final String accessPolicyId) {
+ final AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(accessPolicyId);
+ return createAccessPolicyEntity(accessPolicy);
+ }
+
+ @Override
+ public AccessPolicyEntity getAccessPolicy(final RequestAction requestAction, final String resource) {
+ Authorizable authorizable;
+ try {
+ authorizable = authorizableLookup.getAuthorizableFromResource(resource);
+ } catch (final ResourceNotFoundException e) {
+ // unable to find the underlying authorizable... user authorized based on top level /policies... create
+ // an anonymous authorizable to attempt to locate an existing policy for this resource
+ authorizable = new Authorizable() {
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return new Resource() {
+ @Override
+ public String getIdentifier() {
+ return resource;
+ }
+
+ @Override
+ public String getName() {
+ return resource;
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "Policy " + resource;
+ }
+ };
+ }
+ };
+ }
+
+ final AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(requestAction, authorizable);
+ return createAccessPolicyEntity(accessPolicy);
+ }
+
+ private AccessPolicyEntity createAccessPolicyEntity(final AccessPolicy accessPolicy) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(accessPolicy.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getAccessPolicyById(accessPolicy.getIdentifier()));
+ final ComponentReferenceEntity componentReference = createComponentReferenceEntity(accessPolicy.getResource());
+ return entityFactory.createAccessPolicyEntity(
+ dtoFactory.createAccessPolicyDto(accessPolicy,
+ accessPolicy.getGroups().stream().map(mapUserGroupIdToTenantEntity(false)).collect(Collectors.toSet()),
+ accessPolicy.getUsers().stream().map(mapUserIdToTenantEntity(false)).collect(Collectors.toSet()), componentReference),
+ revision, permissions);
+ }
+
+ @Override
+ public UserEntity getUser(final String userId) {
+ final User user = userDAO.getUser(userId);
+ return createUserEntity(user, true);
+ }
+
+ @Override
+ public Set<UserEntity> getUsers() {
+ final Set<User> users = userDAO.getUsers();
+ return users.stream()
+ .map(user -> createUserEntity(user, false))
+ .collect(Collectors.toSet());
+ }
+
+ private UserEntity createUserEntity(final User user, final boolean enforceUserExistence) {
+ final RevisionDTO userRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(user.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ final Set<TenantEntity> userGroups = userGroupDAO.getUserGroupsForUser(user.getIdentifier()).stream()
+ .map(g -> g.getIdentifier()).map(mapUserGroupIdToTenantEntity(enforceUserExistence)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = userGroupDAO.getAccessPoliciesForUser(user.getIdentifier()).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ return entityFactory.createUserEntity(dtoFactory.createUserDto(user, userGroups, policyEntities), userRevision, permissions);
+ }
+
+ private UserGroupEntity createUserGroupEntity(final Group userGroup, final boolean enforceGroupExistence) {
+ final RevisionDTO userGroupRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(userGroup.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
+ final Set<TenantEntity> users = userGroup.getUsers().stream().map(mapUserIdToTenantEntity(enforceGroupExistence)).collect(Collectors.toSet());
+ final Set<AccessPolicySummaryEntity> policyEntities = userGroupDAO.getAccessPoliciesForUserGroup(userGroup.getIdentifier()).stream()
+ .map(ap -> createAccessPolicySummaryEntity(ap)).collect(Collectors.toSet());
+ return entityFactory.createUserGroupEntity(dtoFactory.createUserGroupDto(userGroup, users, policyEntities), userGroupRevision, permissions);
+ }
+
+ @Override
+ public UserGroupEntity getUserGroup(final String userGroupId) {
+ final Group userGroup = userGroupDAO.getUserGroup(userGroupId);
+ return createUserGroupEntity(userGroup, true);
+ }
+
+ @Override
+ public Set<UserGroupEntity> getUserGroups() {
+ final Set<Group> userGroups = userGroupDAO.getUserGroups();
+ return userGroups.stream()
+ .map(userGroup -> createUserGroupEntity(userGroup, false))
+ .collect(Collectors.toSet());
+ }
+
+ private LabelEntity createLabelEntity(final Label label) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(label.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(label);
+ return entityFactory.createLabelEntity(dtoFactory.createLabelDto(label), revision, permissions);
+ }
+
+ @Override
+ public Set<LabelEntity> getLabels(final String groupId) {
+ final Set<Label> labels = labelDAO.getLabels(groupId);
+ return labels.stream()
+ .map(label -> createLabelEntity(label))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public LabelEntity getLabel(final String labelId) {
+ final Label label = labelDAO.getLabel(labelId);
+ return createLabelEntity(label);
+ }
+
+ private FunnelEntity createFunnelEntity(final Funnel funnel) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(funnel.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(funnel);
+ return entityFactory.createFunnelEntity(dtoFactory.createFunnelDto(funnel), revision, permissions);
+ }
+
+ @Override
+ public Set<FunnelEntity> getFunnels(final String groupId) {
+ final Set<Funnel> funnels = funnelDAO.getFunnels(groupId);
+ return funnels.stream()
+ .map(funnel -> createFunnelEntity(funnel))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public FunnelEntity getFunnel(final String funnelId) {
+ final Funnel funnel = funnelDAO.getFunnel(funnelId);
+ return createFunnelEntity(funnel);
+ }
+
+ private PortEntity createInputPortEntity(final Port port) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(port.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port, NiFiUserUtils.getNiFiUser());
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port), NiFiUserUtils.getNiFiUser());
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(port.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ private PortEntity createOutputPortEntity(final Port port) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(port.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port, NiFiUserUtils.getNiFiUser());
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port), NiFiUserUtils.getNiFiUser());
+ final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(port.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public Set<PortEntity> getInputPorts(final String groupId) {
+ final Set<Port> inputPorts = inputPortDAO.getPorts(groupId);
+ return inputPorts.stream()
+ .map(port -> createInputPortEntity(port))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<PortEntity> getOutputPorts(final String groupId) {
+ final Set<Port> ports = outputPortDAO.getPorts(groupId);
+ return ports.stream()
+ .map(port -> createOutputPortEntity(port))
+ .collect(Collectors.toSet());
+ }
+
+ private ProcessGroupEntity createProcessGroupEntity(final ProcessGroup group) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(group.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(group);
+ final ProcessGroupStatusDTO status = dtoFactory.createConciseProcessGroupStatusDto(controllerFacade.getProcessGroupStatus(group.getIdentifier()));
+ final List<BulletinEntity> bulletins = getProcessGroupBulletins(group);
+ return entityFactory.createProcessGroupEntity(dtoFactory.createProcessGroupDto(group), revision, permissions, status, bulletins);
+ }
+
+ private List<BulletinEntity> getProcessGroupBulletins(final ProcessGroup group) {
+ final List<Bulletin> bulletins = new ArrayList<>(bulletinRepository.findBulletinsForGroupBySource(group.getIdentifier()));
+
+ for (final ProcessGroup descendantGroup : group.findAllProcessGroups()) {
+ bulletins.addAll(bulletinRepository.findBulletinsForGroupBySource(descendantGroup.getIdentifier()));
+ }
+
+ List<BulletinEntity> bulletinEntities = new ArrayList<>();
+ for (final Bulletin bulletin : bulletins) {
+ bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin), authorizeBulletin(bulletin)));
+ }
+
+ return pruneAndSortBulletins(bulletinEntities, BulletinRepository.MAX_BULLETINS_PER_COMPONENT);
+ }
+
+ private List<BulletinEntity> pruneAndSortBulletins(final List<BulletinEntity> bulletinEntities, final int maxBulletins) {
+ // sort the bulletins
+ Collections.sort(bulletinEntities, new Comparator<BulletinEntity>() {
+ @Override
+ public int compare(BulletinEntity o1, BulletinEntity o2) {
+ if (o1 == null && o2 == null) {
+ return 0;
+ }
+ if (o1 == null) {
+ return 1;
+ }
+ if (o2 == null) {
+ return -1;
+ }
+
+ return -Long.compare(o1.getId(), o2.getId());
+ }
+ });
+
+ // prune the response to only include the max number of bulletins
+ if (bulletinEntities.size() > maxBulletins) {
+ return bulletinEntities.subList(0, maxBulletins);
+ } else {
+ return bulletinEntities;
+ }
+ }
+
+ @Override
+ public Set<ProcessGroupEntity> getProcessGroups(final String parentGroupId) {
+ final Set<ProcessGroup> groups = processGroupDAO.getProcessGroups(parentGroupId);
+ return groups.stream()
+ .map(group -> createProcessGroupEntity(group))
+ .collect(Collectors.toSet());
+ }
+
+ private RemoteProcessGroupEntity createRemoteGroupEntity(final RemoteProcessGroup rpg, final NiFiUser user) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(rpg.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(rpg, user);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(rpg), user);
+ final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(rpg, controllerFacade.getRemoteProcessGroupStatus(rpg.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(rpg.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createRemoteProcessGroupEntity(dtoFactory.createRemoteProcessGroupDto(rpg), revision, permissions, operatePermissions, status, bulletinEntities);
+ }
+
+ @Override
+ public Set<RemoteProcessGroupEntity> getRemoteProcessGroups(final String groupId) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final Set<RemoteProcessGroup> rpgs = remoteProcessGroupDAO.getRemoteProcessGroups(groupId);
+ return rpgs.stream()
+ .map(rpg -> createRemoteGroupEntity(rpg, user))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public PortEntity getInputPort(final String inputPortId) {
+ final Port port = inputPortDAO.getPort(inputPortId);
+ return createInputPortEntity(port);
+ }
+
+ @Override
+ public PortStatusEntity getInputPortStatus(final String inputPortId) {
+ final Port inputPort = inputPortDAO.getPort(inputPortId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(inputPort);
+ final PortStatusDTO dto = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(inputPortId));
+ return entityFactory.createPortStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public PortEntity getOutputPort(final String outputPortId) {
+ final Port port = outputPortDAO.getPort(outputPortId);
+ return createOutputPortEntity(port);
+ }
+
+ @Override
+ public PortStatusEntity getOutputPortStatus(final String outputPortId) {
+ final Port outputPort = outputPortDAO.getPort(outputPortId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(outputPort);
+ final PortStatusDTO dto = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(outputPortId));
+ return entityFactory.createPortStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public RemoteProcessGroupEntity getRemoteProcessGroup(final String remoteProcessGroupId) {
+ final RemoteProcessGroup rpg = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId);
+ return createRemoteGroupEntity(rpg, NiFiUserUtils.getNiFiUser());
+ }
+
+ @Override
+ public RemoteProcessGroupStatusEntity getRemoteProcessGroupStatus(final String id) {
+ final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(id);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
+ final RemoteProcessGroupStatusDTO dto = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroup, controllerFacade.getRemoteProcessGroupStatus(id));
+ return entityFactory.createRemoteProcessGroupStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public StatusHistoryEntity getRemoteProcessGroupStatusHistory(final String id) {
+ final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(id);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
+ final StatusHistoryDTO dto = controllerFacade.getRemoteProcessGroupStatusHistory(id);
+ return entityFactory.createStatusHistoryEntity(dto, permissions);
+ }
+
+ @Override
+ public CurrentUserEntity getCurrentUser() {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final CurrentUserEntity entity = new CurrentUserEntity();
+ entity.setIdentity(user.getIdentity());
+ entity.setAnonymous(user.isAnonymous());
+ entity.setProvenancePermissions(dtoFactory.createPermissionsDto(authorizableLookup.getProvenance()));
+ entity.setCountersPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getCounters()));
+ entity.setTenantsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getTenant()));
+ entity.setControllerPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getController()));
+ entity.setPoliciesPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getPolicies()));
+ entity.setSystemPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getSystem()));
+ entity.setCanVersionFlows(CollectionUtils.isNotEmpty(flowRegistryClient.getRegistryIdentifiers()));
+
+ entity.setRestrictedComponentsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents()));
+
+ final Set<ComponentRestrictionPermissionDTO> componentRestrictionPermissions = new HashSet<>();
+ Arrays.stream(RequiredPermission.values()).forEach(requiredPermission -> {
+ final PermissionsDTO restrictionPermissions = dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents(requiredPermission));
+
+ final RequiredPermissionDTO requiredPermissionDto = new RequiredPermissionDTO();
+ requiredPermissionDto.setId(requiredPermission.getPermissionIdentifier());
+ requiredPermissionDto.setLabel(requiredPermission.getPermissionLabel());
+
+ final ComponentRestrictionPermissionDTO componentRestrictionPermissionDto = new ComponentRestrictionPermissionDTO();
+ componentRestrictionPermissionDto.setRequiredPermission(requiredPermissionDto);
+ componentRestrictionPermissionDto.setPermissions(restrictionPermissions);
+
+ componentRestrictionPermissions.add(componentRestrictionPermissionDto);
+ });
+ entity.setComponentRestrictionPermissions(componentRestrictionPermissions);
+
+ return entity;
+ }
+
+ @Override
+ public ProcessGroupFlowEntity getProcessGroupFlow(final String groupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+
+ // Get the Process Group Status but we only need a status depth of one because for any child process group,
+ // we ignore the status of each individual components. I.e., if Process Group A has child Group B, and child Group B
+ // has a Processor, we don't care about the individual stats of that Processor because the ProcessGroupFlowEntity
+ // doesn't include that anyway. So we can avoid including the information in the status that is returned.
+ final ProcessGroupStatus groupStatus = controllerFacade.getProcessGroupStatus(groupId, 1);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ return entityFactory.createProcessGroupFlowEntity(dtoFactory.createProcessGroupFlowDto(processGroup, groupStatus, revisionManager, this::getProcessGroupBulletins), permissions);
+ }
+
+ @Override
+ public ProcessGroupEntity getProcessGroup(final String groupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ return createProcessGroupEntity(processGroup);
+ }
+
+ private ControllerServiceEntity createControllerServiceEntity(final ControllerServiceNode serviceNode, final Set<String> serviceIds) {
+ final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(serviceNode);
+
+ final ControllerServiceReference ref = serviceNode.getReferences();
+ final ControllerServiceReferencingComponentsEntity referencingComponentsEntity = createControllerServiceReferencingComponentsEntity(ref, serviceIds);
+ dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(serviceNode.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(serviceNode, NiFiUserUtils.getNiFiUser());
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(serviceNode), NiFiUserUtils.getNiFiUser());
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(serviceNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createControllerServiceEntity(dto, revision, permissions, operatePermissions, bulletinEntities);
+ }
+
+ @Override
+ public VariableRegistryEntity getVariableRegistry(final String groupId, final boolean includeAncestorGroups) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ if (processGroup == null) {
+ throw new ResourceNotFoundException("Could not find group with ID " + groupId);
+ }
+
+ return createVariableRegistryEntity(processGroup, includeAncestorGroups);
+ }
+
+ private VariableRegistryEntity createVariableRegistryEntity(final ProcessGroup processGroup, final boolean includeAncestorGroups) {
+ final VariableRegistryDTO registryDto = dtoFactory.createVariableRegistryDto(processGroup, revisionManager);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(processGroup.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+
+ if (includeAncestorGroups) {
+ ProcessGroup parent = processGroup.getParent();
+ while (parent != null) {
+ final PermissionsDTO parentPerms = dtoFactory.createPermissionsDto(parent);
+ if (Boolean.TRUE.equals(parentPerms.getCanRead())) {
+ final VariableRegistryDTO parentRegistryDto = dtoFactory.createVariableRegistryDto(parent, revisionManager);
+ final Set<VariableEntity> parentVariables = parentRegistryDto.getVariables();
+ registryDto.getVariables().addAll(parentVariables);
+ }
+
+ parent = parent.getParent();
+ }
+ }
+
+ return entityFactory.createVariableRegistryEntity(registryDto, revision, permissions);
+ }
+
+ @Override
+ public VariableRegistryEntity populateAffectedComponents(final VariableRegistryDTO variableRegistryDto) {
+ final String groupId = variableRegistryDto.getProcessGroupId();
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ if (processGroup == null) {
+ throw new ResourceNotFoundException("Could not find group with ID " + groupId);
+ }
+
+ final VariableRegistryDTO registryDto = dtoFactory.populateAffectedComponents(variableRegistryDto, processGroup, revisionManager);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(processGroup.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ return entityFactory.createVariableRegistryEntity(registryDto, revision, permissions);
+ }
+
+ @Override
+ public Set<ControllerServiceEntity> getControllerServices(final String groupId, final boolean includeAncestorGroups, final boolean includeDescendantGroups) {
+ final Set<ControllerServiceNode> serviceNodes = controllerServiceDAO.getControllerServices(groupId, includeAncestorGroups, includeDescendantGroups);
+ final Set<String> serviceIds = serviceNodes.stream().map(service -> service.getIdentifier()).collect(Collectors.toSet());
+
+ return serviceNodes.stream()
+ .map(serviceNode -> createControllerServiceEntity(serviceNode, serviceIds))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ControllerServiceEntity getControllerService(final String controllerServiceId) {
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId);
+ return createControllerServiceEntity(controllerService, Sets.newHashSet(controllerServiceId));
+ }
+
+ @Override
+ public PropertyDescriptorDTO getControllerServicePropertyDescriptor(final String id, final String property) {
+ final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(id);
+ PropertyDescriptor descriptor = controllerService.getControllerServiceImplementation().getPropertyDescriptor(property);
+
+ // return an invalid descriptor if the controller service doesn't support this property
+ if (descriptor == null) {
+ descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
+ }
+
+ final String groupId = controllerService.getProcessGroup() == null ? null : controllerService.getProcessGroup().getIdentifier();
+ return dtoFactory.createPropertyDescriptorDto(descriptor, groupId);
+ }
+
+ @Override
+ public ControllerServiceReferencingComponentsEntity getControllerServiceReferencingComponents(final String controllerServiceId) {
+ final ControllerServiceNode service = controllerServiceDAO.getControllerService(controllerServiceId);
+ final ControllerServiceReference ref = service.getReferences();
+ return createControllerServiceReferencingComponentsEntity(ref, Sets.newHashSet(controllerServiceId));
+ }
+
+ private ReportingTaskEntity createReportingTaskEntity(final ReportingTaskNode reportingTask) {
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(reportingTask.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createReportingTaskEntity(dtoFactory.createReportingTaskDto(reportingTask), revision, permissions, operatePermissions, bulletinEntities);
+ }
+
+ @Override
+ public Set<ReportingTaskEntity> getReportingTasks() {
+ final Set<ReportingTaskNode> reportingTasks = reportingTaskDAO.getReportingTasks();
+ return reportingTasks.stream()
+ .map(reportingTask -> createReportingTaskEntity(reportingTask))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ReportingTaskEntity getReportingTask(final String reportingTaskId) {
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId);
+ return createReportingTaskEntity(reportingTask);
+ }
+
+ @Override
+ public PropertyDescriptorDTO getReportingTaskPropertyDescriptor(final String id, final String property) {
+ final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(id);
+ PropertyDescriptor descriptor = reportingTask.getReportingTask().getPropertyDescriptor(property);
+
+ // return an invalid descriptor if the reporting task doesn't support this property
+ if (descriptor == null) {
+ descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
+ }
+
+ return dtoFactory.createPropertyDescriptorDto(descriptor, null);
+ }
+
+ @Override
+ public StatusHistoryEntity getProcessGroupStatusHistory(final String groupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ final StatusHistoryDTO dto = controllerFacade.getProcessGroupStatusHistory(groupId);
+ return entityFactory.createStatusHistoryEntity(dto, permissions);
+ }
+
+ @Override
+ public VersionControlComponentMappingEntity registerFlowWithFlowRegistry(final String groupId, final StartVersionControlRequestEntity requestEntity) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+
+ final VersionControlInformation currentVci = processGroup.getVersionControlInformation();
+ final int expectedVersion = currentVci == null ? 1 : currentVci.getVersion() + 1;
+
+ // Create a VersionedProcessGroup snapshot of the flow as it is currently.
+ final InstantiatedVersionedProcessGroup versionedProcessGroup = createFlowSnapshot(groupId);
+
+ final VersionedFlowDTO versionedFlowDto = requestEntity.getVersionedFlow();
+ final String flowId = versionedFlowDto.getFlowId() == null ? UUID.randomUUID().toString() : versionedFlowDto.getFlowId();
+
+ final VersionedFlow versionedFlow = new VersionedFlow();
+ versionedFlow.setBucketIdentifier(versionedFlowDto.getBucketId());
+ versionedFlow.setCreatedTimestamp(System.currentTimeMillis());
+ versionedFlow.setDescription(versionedFlowDto.getDescription());
+ versionedFlow.setModifiedTimestamp(versionedFlow.getCreatedTimestamp());
+ versionedFlow.setName(versionedFlowDto.getFlowName());
+ versionedFlow.setIdentifier(flowId);
+
+ // Add the Versioned Flow and first snapshot to the Flow Registry
+ final String registryId = requestEntity.getVersionedFlow().getRegistryId();
+ final VersionedFlowSnapshot registeredSnapshot;
+ final VersionedFlow registeredFlow;
+
+ String action = "create the flow";
+ try {
+ // first, create the flow in the registry, if necessary
+ if (versionedFlowDto.getFlowId() == null) {
+ registeredFlow = registerVersionedFlow(registryId, versionedFlow);
+ } else {
+ registeredFlow = getVersionedFlow(registryId, versionedFlowDto.getBucketId(), versionedFlowDto.getFlowId());
+ }
+
+ action = "add the local flow to the Flow Registry as the first Snapshot";
+
+ // add first snapshot to the flow in the registry
+ registeredSnapshot = registerVersionedFlowSnapshot(registryId, registeredFlow, versionedProcessGroup, versionedFlowDto.getComments(), expectedVersion);
+ } catch (final NiFiRegistryException e) {
+ throw new IllegalArgumentException(e.getLocalizedMessage());
+ } catch (final IOException ioe) {
+ throw new IllegalStateException("Failed to communicate with Flow Registry when attempting to " + action);
+ }
+
+ final Bucket bucket = registeredSnapshot.getBucket();
+ final VersionedFlow flow = registeredSnapshot.getFlow();
+
+ // Update the Process Group with the new VersionControlInformation. (Send this to all nodes).
+ final VersionControlInformationDTO vci = new VersionControlInformationDTO();
+ vci.setBucketId(bucket.getIdentifier());
+ vci.setBucketName(bucket.getName());
+ vci.setFlowId(flow.getIdentifier());
+ vci.setFlowName(flow.getName());
+ vci.setFlowDescription(flow.getDescription());
+ vci.setGroupId(groupId);
+ vci.setRegistryId(registryId);
+ vci.setRegistryName(getFlowRegistryName(registryId));
+ vci.setVersion(registeredSnapshot.getSnapshotMetadata().getVersion());
+ vci.setState(VersionedFlowState.UP_TO_DATE.name());
+
+ final Map<String, String> mapping = dtoFactory.createVersionControlComponentMappingDto(versionedProcessGroup);
+
+ final Revision groupRevision = revisionManager.getRevision(groupId);
+ final RevisionDTO groupRevisionDto = dtoFactory.createRevisionDTO(groupRevision);
+
+ final VersionControlComponentMappingEntity entity = new VersionControlComponentMappingEntity();
+ entity.setVersionControlInformation(vci);
+ entity.setProcessGroupRevision(groupRevisionDto);
+ entity.setVersionControlComponentMapping(mapping);
+ return entity;
+ }
+
+ @Override
+ public VersionedFlow deleteVersionedFlow(final String registryId, final String bucketId, final String flowId) {
+ final FlowRegistry registry = flowRegistryClient.getFlowRegistry(registryId);
+ if (registry == null) {
+ throw new IllegalArgumentException("No Flow Registry exists with ID " + registryId);
+ }
+
+ try {
+ return registry.deleteVersionedFlow(bucketId, flowId, NiFiUserUtils.getNiFiUser());
+ } catch (final IOException | NiFiRegistryException e) {
+ throw new NiFiCoreException("Failed to remove flow from Flow Registry due to " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public VersionControlInformationEntity getVersionControlInformation(final String groupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ final VersionControlInformation versionControlInfo = processGroup.getVersionControlInformation();
+ if (versionControlInfo == null) {
+ return null;
+ }
+
+ final VersionControlInformationDTO versionControlDto = dtoFactory.createVersionControlInformationDto(processGroup);
+ final RevisionDTO groupRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(groupId));
+ return entityFactory.createVersionControlInformationEntity(versionControlDto, groupRevision);
+ }
+
+ private InstantiatedVersionedProcessGroup createFlowSnapshot(final String processGroupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(processGroupId);
+ final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(controllerFacade.getExtensionManager());
+ final InstantiatedVersionedProcessGroup versionedGroup = mapper.mapProcessGroup(processGroup, controllerFacade.getControllerServiceProvider(), flowRegistryClient, false);
+ return versionedGroup;
+ }
+
+ @Override
+ public FlowComparisonEntity getLocalModifications(final String processGroupId) {
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(processGroupId);
+ final VersionControlInformation versionControlInfo = processGroup.getVersionControlInformation();
+ if (versionControlInfo == null) {
+ throw new IllegalStateException("Process Group with ID " + processGroupId + " is not under Version Control");
+ }
+
+ final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(versionControlInfo.getRegistryIdentifier());
+ if (flowRegistry == null) {
+ throw new IllegalStateException("Process Group with ID " + processGroupId + " is tracking to a flow in Flow Registry with ID " + versionControlInfo.getRegistryIdentifier()
+ + " but cannot find a Flow Registry with that identifier");
+ }
+
+ final VersionedFlowSnapshot versionedFlowSnapshot;
+ try {
+ versionedFlowSnapshot = flowRegistry.getFlowContents(versionControlInfo.getBucketIdentifier(),
+ versionControlInfo.getFlowIdentifier(), versionControlInfo.getVersion(), true, NiFiUserUtils.getNiFiUser());
+ } catch (final IOException | NiFiRegistryException e) {
+ throw new NiFiCoreException("Failed to retrieve flow with Flow Registry in order to calculate local differences due to " + e.getMessage(), e);
+ }
+
+ final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(controllerFacade.getExtensionManager());
+ final VersionedProcessGroup localGroup = mapper.mapProcessGroup(processGroup, controllerFacade.getControllerServiceProvider(), flowRegistryClient, true);
+ final VersionedProcessGroup registryGroup = versionedFlowSnapshot.getFlowContents();
+
+ final ComparableDataFlow localFlow = new StandardComparableDataFlow("Local Flow", localGroup);
+ final ComparableDataFlow registryFlow = new StandardComparableDataFlow("Versioned Flow", registryGroup);
+
+ final Set<String> ancestorServiceIds = getAncestorGroupServiceIds(processGroup);
+ final FlowComparator flowComparator = new StandardFlowComparator(registryFlow, localFlow, ancestorServiceIds, new ConciseEvolvingDifferenceDescriptor());
+ final FlowComparison flowComparison = flowComparator.compare();
+
+ final Set<ComponentDifferenceDTO> differenceDtos = dtoFactory.createComponentDifferenceDtos(flowComparison);
+
+ final FlowComparisonEntity entity = new FlowComparisonEntity();
+ entity.setComponentDifferences(differenceDtos);
+ return entity;
+ }
+
+ private Set<String> getAncestorGroupServiceIds(final ProcessGroup group) {
+ final Set<String> ancestorServiceIds;
+ ProcessGroup parentGroup = group.getParent();
+
+ if (parentGroup == null) {
+ ancestorServiceIds = Collections.emptySet();
+ } else {
+ ancestorServiceIds = parentGroup.getControllerServices(true).stream()
+ .map(cs -> {
+ // We want to map the Controller Service to its Versioned Component ID, if it has one.
+ // If it does not have one, we want to generate it in the same way that our Flow Mapper does
+ // because this allows us to find the Controller Service when doing a Flow Diff.
+ final Optional<String> versionedId = cs.getVersionedComponentId();
+ if (versionedId.isPresent()) {
+ return versionedId.get();
+ }
+
+ return UUID.nameUUIDFromBytes(cs.getIdentifier().getBytes(StandardCharsets.UTF_8)).toString();
+ })
+ .collect(Collectors.toSet());
+ }
+
+ return ancestorServiceIds;
+ }
+
+ @Override
+ public VersionedFlow registerVersionedFlow(final String registryId, final VersionedFlow flow) {
+ final FlowRegistry registry = flowRegistryClient.getFlowRegistry(registryId);
+ if (registry == null) {
+ throw new ResourceNotFoundException("No Flow Registry exists with ID " + registryId);
+ }
+
+ try {
+ return registry.registerVersionedFlow(flow, NiFiUserUtils.getNiFiUser());
+ } catch (final IOException | NiFiRegistryException e) {
+ throw new NiFiCoreException("Failed to register flow with Flow Registry due to " + e.getMessage(), e);
+ }
+ }
+
+ private VersionedFlow getVersionedFlow(final String registryId, final String bucketId, final String flowId) throws IOException, NiFiRegistryException {
+ final FlowRegistry registry = flowRegistryClient.getFlowRegistry(registryId);
+ if (registry == null) {
+ throw new ResourceNotFoundException("No Flow Registry exists with ID " + registryId);
+ }
+
+ return registry.getVersionedFlow(bucketId, flowId, NiFiUserUtils.getNiFiUser());
+ }
+
+ @Override
+ public VersionedFlowSnapshot registerVersionedFlowSnapshot(final String registryId, final VersionedFlow flow,
+ final VersionedProcessGroup snapshot, final String comments, final int expectedVersion) {
+ final FlowRegistry registry = flowRegistryClient.getFlowRegistry(registryId);
+ if (registry == null) {
+ throw new ResourceNotFoundException("No Flow Registry exists with ID " + registryId);
+ }
+
+ try {
+ return registry.registerVersionedFlowSnapshot(flow, snapshot, comments, expectedVersion, NiFiUserUtils.getNiFiUser());
+ } catch (final IOException | NiFiRegistryException e) {
+ throw new NiFiCoreException("Failed to register flow with Flow Registry due to " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public VersionControlInformationEntity setVersionControlInformation(final Revision revision, final String processGroupId,
+ final VersionControlInformationDTO versionControlInfo, final Map<String, String> versionedComponentMapping) {
+
+ final ProcessGroup group = processGroupDAO.getProcessGroup(processGroupId);
+
+ final RevisionUpdate<VersionControlInformationDTO> snapshot = updateComponent(revision,
+ group,
+ () -> processGroupDAO.updateVersionControlInformation(versionControlInfo, versionedComponentMapping),
+ processGroup -> dtoFactory.createVersionControlInformationDto(processGroup));
+
+ return entityFactory.createVersionControlInformationEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()));
+ }
+
+ @Override
+ public VersionControlInformationEntity deleteVersionControl(final Revision revision, final String processGroupId) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(processGroupId);
+
+ final RevisionUpdate<VersionControlInformationDTO> snapshot = updateComponent(revision,
+ group,
+ () -> processGroupDAO.disconnectVersionControl(processGroupId),
+ processGroup -> dtoFactory.createVersionControlInformationDto(group));
+
+ return entityFactory.createVersionControlInformationEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()));
+ }
+
+ @Override
+ public void verifyCanUpdate(final String groupId, final VersionedFlowSnapshot proposedFlow, final boolean verifyConnectionRemoval, final boolean verifyNotDirty) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ group.verifyCanUpdate(proposedFlow, verifyConnectionRemoval, verifyNotDirty);
+ }
+
+ @Override
+ public void verifyCanSaveToFlowRegistry(final String groupId, final String registryId, final String bucketId, final String flowId) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ group.verifyCanSaveToFlowRegistry(registryId, bucketId, flowId);
+ }
+
+ @Override
+ public void verifyCanRevertLocalModifications(final String groupId, final VersionedFlowSnapshot versionedFlowSnapshot) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(groupId);
+ group.verifyCanRevertLocalModifications();
+
+ // verify that the process group can be updated to the given snapshot. We do not verify that connections can
+ // be removed, because the flow may still be running, and it only matters that the connections can be removed once the components
+ // have been stopped.
+ group.verifyCanUpdate(versionedFlowSnapshot, false, false);
+ }
+
+ @Override
+ public Set<AffectedComponentEntity> getComponentsAffectedByVersionChange(final String processGroupId, final VersionedFlowSnapshot updatedSnapshot) {
+ final ProcessGroup group = processGroupDAO.getProcessGroup(processGroupId);
+
+ final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(controllerFacade.getExtensionManager());
+ final VersionedProcessGroup localContents = mapper.mapProcessGroup(group, controllerFacade.getControllerServiceProvider(), flowRegistryClient, true);
+
+ final ComparableDataFlow localFlow = new StandardComparableDataFlow("Local Flow", localContents);
+ final ComparableDataFlow proposedFlow = new StandardComparableDataFlow("Versioned Flow", updatedSnapshot.getFlowContents());
+
+ final Set<String> ancestorGroupServiceIds = getAncestorGroupServiceIds(group);
+ final FlowComparator flowComparator = new StandardFlowComparator(localFlow, proposedFlow, ancestorGroupServiceIds, new StaticDifferenceDescriptor());
+ final FlowComparison comparison = flowComparator.compare();
+
+ final Set<AffectedComponentEntity> affectedComponents = comparison.getDifferences().stream()
+ .filter(difference -> difference.getDifferenceType() != DifferenceType.COMPONENT_ADDED) // components that are added are not components that will be affected in the local flow.
+ .filter(difference -> difference.getDifferenceType() != DifferenceType.BUNDLE_CHANGED)
+ .filter(FlowDifferenceFilters.FILTER_ADDED_REMOVED_REMOTE_PORTS)
+ .filter(FlowDifferenceFilters.FILTER_IGNORABLE_VERSIONED_FLOW_COORDINATE_CHANGES)
+ .map(difference -> {
+ final VersionedComponent localComponent = difference.getComponentA();
+
+ final String state;
+ switch (localComponent.getComponentType()) {
+ case CONTROLLER_SERVICE:
+ final String serviceId = ((InstantiatedVersionedControllerService) localComponent).getInstanceId();
+ state = controllerServiceDAO.getControllerService(serviceId).getState().name();
+ break;
+ case PROCESSOR:
+ final String processorId = ((InstantiatedVersionedProcessor) localComponent).getInstanceId();
+ state = processorDAO.getProcessor(processorId).getPhysicalScheduledState().name();
+ break;
+ case REMOTE_INPUT_PORT:
+ final InstantiatedVersionedRemoteGroupPort inputPort = (InstantiatedVersionedRemoteGroupPort) localComponent;
+ state = remoteProcessGroupDAO.getRemoteProcessGroup(inputPort.getInstanceGroupId()).getInputPort(inputPort.getInstanceId()).getScheduledState().name();
+ break;
+ case REMOTE_OUTPUT_PORT:
+ final InstantiatedVersionedRemoteGroupPort outputPort = (InstantiatedVersionedRemoteGroupPort) localComponent;
+ state = remoteProcessGroupDAO.getRemoteProcessGroup(outputPort.getInstanceGroupId()).getOutputPort(outputPort.getInstanceId()).getScheduledState().name();
+ break;
+ default:
+ state = null;
+ break;
+ }
+
+ return createAffectedComponentEntity((InstantiatedVersionedComponent) localComponent, localComponent.getComponentType().name(), state);
+ })
+ .collect(Collectors.toCollection(HashSet::new));
+
+ for (final FlowDifference difference : comparison.getDifferences()) {
+ // Ignore these as local differences for now because we can't do anything with it
+ if (difference.getDifferenceType() == DifferenceType.BUNDLE_CHANGED) {
+ continue;
+ }
+
+ // Ignore differences for adding remote ports
+ if (FlowDifferenceFilters.isAddedOrRemovedRemotePort(difference)) {
+ continue;
+ }
+
+ if (FlowDifferenceFilters.isIgnorableVersionedFlowCoordinateChange(difference)) {
+ continue;
+ }
+
+ final VersionedComponent localComponent = difference.getComponentA();
+ if (localComponent == null) {
+ continue;
+ }
+
+ // If any Process Group is removed, consider all components below that Process Group as an affected component
+ if (difference.getDifferenceType() == DifferenceType.COMPONENT_REMOVED && localComponent.getComponentType() == org.apache.nifi.registry.flow.ComponentType.PROCESS_GROUP) {
+ final String localGroupId = ((InstantiatedVersionedProcessGroup) localComponent).getInstanceId();
+ final ProcessGroup localGroup = processGroupDAO.getProcessGroup(localGroupId);
+
+ localGroup.findAllProcessors().stream()
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ localGroup.findAllFunnels().stream()
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ localGroup.findAllInputPorts().stream()
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ localGroup.findAllOutputPorts().stream()
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ localGroup.findAllRemoteProcessGroups().stream()
+ .flatMap(rpg -> Stream.concat(rpg.getInputPorts().stream(), rpg.getOutputPorts().stream()))
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ localGroup.findAllControllerServices().stream()
+ .map(comp -> createAffectedComponentEntity(comp))
+ .forEach(affectedComponents::add);
+ }
+
+ if (localComponent.getComponentType() == org.apache.nifi.registry.flow.ComponentType.CONTROLLER_SERVICE) {
+ final String serviceId = ((InstantiatedVersionedControllerService) localComponent).getInstanceId();
+ final ControllerServiceNode serviceNode = controllerServiceDAO.getControllerService(serviceId);
+
+ final List<ControllerServiceNode> referencingServices = serviceNode.getReferences().findRecursiveReferences(ControllerServiceNode.class);
+ for (final ControllerServiceNode referencingService : referencingServices) {
+ affectedComponents.add(createAffectedComponentEntity(referencingService));
+ }
+
+ final List<ProcessorNode> referencingProcessors = serviceNode.getReferences().findRecursiveReferences(ProcessorNode.class);
+ for (final ProcessorNode referencingProcessor : referencingProcessors) {
+ affectedComponents.add(createAffectedComponentEntity(referencingProcessor));
+ }
+ }
+ }
+
+ // Create a map of all connectable components by versioned component ID to the connectable component itself
+ final Map<String, List<Connectable>> connectablesByVersionId = new HashMap<>();
+ mapToConnectableId(group.findAllFunnels(), connectablesByVersionId);
+ mapToConnectableId(group.findAllInputPorts(), connectablesByVersionId);
+ mapToConnectableId(group.findAllOutputPorts(), connectablesByVersionId);
+ mapToConnectableId(group.findAllProcessors(), connectablesByVersionId);
+
+ final List<RemoteGroupPort> remotePorts = new ArrayList<>();
+ for (final RemoteProcessGroup rpg : group.findAllRemoteProcessGroups()) {
+ remotePorts.addAll(rpg.getInputPorts());
+ remotePorts.addAll(rpg.getOutputPorts());
+ }
+ mapToConnectableId(remotePorts, connectablesByVersionId);
+
+ // If any connection is added or modified, we need to stop both the source (if it exists in the flow currently)
+ // and the destination (if it exists in the flow currently).
+ for (final FlowDifference difference : comparison.getDifferences()) {
+ VersionedComponent component = difference.getComponentA();
+ if (component == null) {
+ component = difference.getComponentB();
+ }
+
+ if (component.getComponentType() != org.apache.nifi.registry.flow.ComponentType.CONNECTION) {
+ continue;
+ }
+
+ final VersionedConnection connection = (VersionedConnection) component;
+
+ final String sourceVersionedId = connection.getSource().getId();
+ final List<Connectable> sources = connectablesByVersionId.get(sourceVersionedId);
+ if (sources != null) {
+ for (final Connectable source : sources) {
+ affectedComponents.add(createAffectedComponentEntity(source));
+ }
+ }
+
+ final String destinationVersionId = connection.getDestination().getId();
+ final List<Connectable> destinations = connectablesByVersionId.get(destinationVersionId);
+ if (destinations != null) {
+ for (final Connectable destination : destinations) {
+ affectedComponents.add(createAffectedComponentEntity(destination));
+ }
+ }
+ }
+
+ return affectedComponents;
+ }
+
+ private void mapToConnectableId(final Collection<? extends Connectable> connectables, final Map<String, List<Connectable>> destination) {
+ for (final Connectable connectable : connectables) {
+ final Optional<String> versionedIdOption = connectable.getVersionedComponentId();
+
+ // Determine the Versioned ID by using the ID that is assigned, if one is. Otherwise,
+ // we will calculate the Versioned ID. This allows us to map connectables that currently are not under
+ // version control. We have to do this so that if we are changing flow versions and have a component that is running and it does not exist
+ // in the Versioned Flow, we still need to be able to create an AffectedComponentDTO for it.
+ final String versionedId;
+ if (versionedIdOption.isPresent()) {
+ versionedId = versionedIdOption.get();
+ } else {
+ versionedId = UUID.nameUUIDFromBytes(connectable.getIdentifier().getBytes(StandardCharsets.UTF_8)).toString();
+ }
+
+ final List<Connectable> byVersionedId = destination.computeIfAbsent(versionedId, key -> new ArrayList<>());
+ byVersionedId.add(connectable);
+ }
+ }
+
+
+ private AffectedComponentEntity createAffectedComponentEntity(final Connectable connectable) {
+ final AffectedComponentEntity entity = new AffectedComponentEntity();
+ entity.setRevision(dtoFactory.createRevisionDTO(revisionManager.getRevision(connectable.getIdentifier())));
+ entity.setId(connectable.getIdentifier());
+
+ final Authorizable authorizable = getAuthorizable(connectable);
+ final PermissionsDTO permissionsDto = dtoFactory.createPermissionsDto(authorizable);
+ entity.setPermissions(permissionsDto);
+
+ final AffectedComponentDTO dto = new AffectedComponentDTO();
+ dto.setId(connectable.getIdentifier());
+ dto.setReferenceType(connectable.getConnectableType().name());
+ dto.setState(connectable.getScheduledState().name());
+
+ final String groupId = connectable instanceof RemoteGroupPort ? ((RemoteGroupPort) connectable).getRemoteProcessGroup().getIdentifier() : connectable.getProcessGroupIdentifier();
+ dto.setProcessGroupId(groupId);
+
+ entity.setComponent(dto);
+ return entity;
+ }
+
+ private AffectedComponentEntity createAffectedComponentEntity(final ControllerServiceNode serviceNode) {
+ final AffectedComponentEntity entity = new AffectedComponentEntity();
+ entity.setRevision(dtoFactory.createRevisionDTO(revisionManager.getRevision(serviceNode.getIdentifier())));
+ entity.setId(serviceNode.getIdentifier());
+
+ final Authorizable authorizable = authorizableLookup.getControllerService(serviceNode.getIdentifier()).getAuthorizable();
+ final PermissionsDTO permissionsDto = dtoFactory.createPermissionsDto(authorizable);
+ entity.setPermissions(permissionsDto);
+
+ final AffectedComponentDTO dto = new AffectedComponentDTO();
+ dto.setId(serviceNode.getIdentifier());
+ dto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_CONTROLLER_SERVICE);
+ dto.setProcessGroupId(serviceNode.getProcessGroupIdentifier());
+ dto.setState(serviceNode.getState().name());
+
+ entity.setComponent(dto);
+ return entity;
+ }
+
+ private AffectedComponentEntity createAffectedComponentEntity(final InstantiatedVersionedComponent instance, final String componentTypeName, final String componentState) {
+ final AffectedComponentEntity entity = new AffectedComponentEntity();
+ entity.setRevision(dtoFactory.createRevisionDTO(revisionManager.getRevision(instance.getInstanceId())));
+ entity.setId(instance.getInstanceId());
+
+ final Authorizable authorizable = getAuthorizable(componentTypeName, instance);
+ final PermissionsDTO permissionsDto = dtoFactory.createPermissionsDto(authorizable);
+ entity.setPermissions(permissionsDto);
+
+ final AffectedComponentDTO dto = new AffectedComponentDTO();
+ dto.setId(instance.getInstanceId());
+ dto.setReferenceType(componentTypeName);
+ dto.setProcessGroupId(instance.getInstanceGroupId());
+ dto.setState(componentState);
+
+ entity.setComponent(dto);
+ return entity;
+ }
+
+
+ private Authorizable getAuthorizable(final Connectable connectable) {
+ switch (connectable.getConnectableType()) {
+ case REMOTE_INPUT_PORT:
+ case REMOTE_OUTPUT_PORT:
+ final String rpgId = ((RemoteGroupPort) connectable).getRemoteProcessGroup().getIdentifier();
+ return authorizableLookup.getRemoteProcessGroup(rpgId);
+ default:
+ return authorizableLookup.getLocalConnectable(connectable.getIdentifier());
+ }
+ }
+
+ private Authorizable getAuthorizable(final String componentTypeName, final InstantiatedVersionedComponent versionedComponent) {
+ final String componentId = versionedComponent.getInstanceId();
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.CONTROLLER_SERVICE.name())) {
+ return authorizableLookup.getControllerService(componentId).getAuthorizable();
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.CONNECTION.name())) {
+ return authorizableLookup.getConnection(componentId).getAuthorizable();
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.FUNNEL.name())) {
+ return authorizableLookup.getFunnel(componentId);
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.INPUT_PORT.name())) {
+ return authorizableLookup.getInputPort(componentId);
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.OUTPUT_PORT.name())) {
+ return authorizableLookup.getOutputPort(componentId);
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.LABEL.name())) {
+ return authorizableLookup.getLabel(componentId);
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.PROCESS_GROUP.name())) {
+ return authorizableLookup.getProcessGroup(componentId).getAuthorizable();
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.PROCESSOR.name())) {
+ return authorizableLookup.getProcessor(componentId).getAuthorizable();
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.REMOTE_INPUT_PORT.name())) {
+ return authorizableLookup.getRemoteProcessGroup(versionedComponent.getInstanceGroupId());
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.REMOTE_OUTPUT_PORT.name())) {
+ return authorizableLookup.getRemoteProcessGroup(versionedComponent.getInstanceGroupId());
+ }
+
+ if (componentTypeName.equals(org.apache.nifi.registry.flow.ComponentType.REMOTE_PROCESS_GROUP.name())) {
+ return authorizableLookup.getRemoteProcessGroup(componentId);
+ }
+
+ return null;
+ }
+
+ @Override
+ public VersionedFlowSnapshot getVersionedFlowSnapshot(final VersionControlInformationDTO versionControlInfo, final boolean fetchRemoteFlows) {
+ final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(versionControlInfo.getRegistryId());
+ if (flowRegistry == null) {
+ throw new ResourceNotFoundException("Could not find any Flow Registry registered with identifier " + versionControlInfo.getRegistryId());
+ }
+
+ final VersionedFlowSnapshot snapshot;
+ try {
+ snapshot = flowRegistry.getFlowContents(versionControlInfo.getBucketId(), versionControlInfo.getFlowId(), versionControlInfo.getVersion(), fetchRemoteFlows, NiFiUserUtils.getNiFiUser());
+ } catch (final NiFiRegistryException | IOException e) {
+ logger.error(e.getMessage(), e);
+ throw new IllegalArgumentException("The Flow Registry with ID " + versionControlInfo.getRegistryId() + " reports that no Flow exists with Bucket "
+ + versionControlInfo.getBucketId() + ", Flow " + versionControlInfo.getFlowId() + ", Version " + versionControlInfo.getVersion());
+ }
+
+ return snapshot;
+ }
+
+ @Override
+ public String getFlowRegistryName(final String flowRegistryId) {
+ final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(flowRegistryId);
+ return flowRegistry == null ? flowRegistryId : flowRegistry.getName();
+ }
+
+ private List<Revision> getComponentRevisions(final ProcessGroup processGroup, final boolean includeGroupRevision) {
+ final List<Revision> revisions = new ArrayList<>();
+ if (includeGroupRevision) {
+ revisions.add(revisionManager.getRevision(processGroup.getIdentifier()));
+ }
+
+ processGroup.findAllConnections().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllControllerServices().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllFunnels().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllInputPorts().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllOutputPorts().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllLabels().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllProcessGroups().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllProcessors().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllRemoteProcessGroups().stream()
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+ processGroup.findAllRemoteProcessGroups().stream()
+ .flatMap(rpg -> Stream.concat(rpg.getInputPorts().stream(), rpg.getOutputPorts().stream()))
+ .map(component -> revisionManager.getRevision(component.getIdentifier()))
+ .forEach(revisions::add);
+
+ return revisions;
+ }
+
+ @Override
+ public ProcessGroupEntity updateProcessGroupContents(final Revision revision, final String groupId, final VersionControlInformationDTO versionControlInfo,
+ final VersionedFlowSnapshot proposedFlowSnapshot, final String componentIdSeed, final boolean verifyNotModified, final boolean updateSettings, final boolean updateDescendantVersionedFlows) {
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ final List<Revision> revisions = getComponentRevisions(processGroup, false);
+ revisions.add(revision);
+
+ final RevisionClaim revisionClaim = new StandardRevisionClaim(revisions);
+
+ final RevisionUpdate<ProcessGroupDTO> revisionUpdate = revisionManager.updateRevision(revisionClaim, user, new UpdateRevisionTask<ProcessGroupDTO>() {
+ @Override
+ public RevisionUpdate<ProcessGroupDTO> update() {
+ // update the Process Group
+ processGroupDAO.updateProcessGroupFlow(groupId, proposedFlowSnapshot, versionControlInfo, componentIdSeed, verifyNotModified, updateSettings, updateDescendantVersionedFlows);
+
+ // update the revisions
+ final Set<Revision> updatedRevisions = revisions.stream()
+ .map(rev -> revisionManager.getRevision(rev.getComponentId()).incrementRevision(revision.getClientId()))
+ .collect(Collectors.toSet());
+
+ // save
+ controllerFacade.save();
+
+ // gather details for response
+ final ProcessGroupDTO dto = dtoFactory.createProcessGroupDto(processGroup);
+
+ final Revision updatedRevision = revisionManager.getRevision(groupId).incrementRevision(revision.getClientId());
+ final FlowModification lastModification = new FlowModification(updatedRevision, user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastModification, updatedRevisions);
+ }
+ });
+
+ final FlowModification lastModification = revisionUpdate.getLastModification();
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
+ final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(lastModification);
+ final ProcessGroupStatusDTO status = dtoFactory.createConciseProcessGroupStatusDto(controllerFacade.getProcessGroupStatus(processGroup.getIdentifier()));
+ final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processGroup.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ return entityFactory.createProcessGroupEntity(revisionUpdate.getComponent(), updatedRevision, permissions, status, bulletinEntities);
+ }
+
+ private AuthorizationResult authorizeAction(final Action action) {
+ final String sourceId = action.getSourceId();
+ final Component type = action.getSourceType();
+
+ Authorizable authorizable;
+ try {
+ switch (type) {
+ case Processor:
+ authorizable = authorizableLookup.getProcessor(sourceId).getAuthorizable();
+ break;
+ case ReportingTask:
+ authorizable = authorizableLookup.getReportingTask(sourceId).getAuthorizable();
+ break;
+ case ControllerService:
+ authorizable = authorizableLookup.getControllerService(sourceId).getAuthorizable();
+ break;
+ case Controller:
+ authorizable = controllerFacade;
+ break;
+ case InputPort:
+ authorizable = authorizableLookup.getInputPort(sourceId);
+ break;
+ case OutputPort:
+ authorizable = authorizableLookup.getOutputPort(sourceId);
+ break;
+ case ProcessGroup:
+ authorizable = authorizableLookup.getProcessGroup(sourceId).getAuthorizable();
+ break;
+ case RemoteProcessGroup:
+ authorizable = authorizableLookup.getRemoteProcessGroup(sourceId);
+ break;
+ case Funnel:
+ authorizable = authorizableLookup.getFunnel(sourceId);
+ break;
+ case Connection:
+ authorizable = authorizableLookup.getConnection(sourceId).getAuthorizable();
+ break;
+ case AccessPolicy:
+ authorizable = authorizableLookup.getAccessPolicyById(sourceId);
+ break;
+ case User:
+ case UserGroup:
+ authorizable = authorizableLookup.getTenant();
+ break;
+ default:
+ throw new WebApplicationException(Response.serverError().entity("An unexpected type of component is the source of this action.").build());
+ }
+ } catch (final ResourceNotFoundException e) {
+ // if the underlying component is gone, use the controller to see if permissions should be allowed
+ authorizable = controllerFacade;
+ }
+
+ // perform the authorization
+ return authorizable.checkAuthorization(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ }
+
+ @Override
+ public HistoryDTO getActions(final HistoryQueryDTO historyQueryDto) {
+ // extract the query criteria
+ final HistoryQuery historyQuery = new HistoryQuery();
+ historyQuery.setStartDate(historyQueryDto.getStartDate());
+ historyQuery.setEndDate(historyQueryDto.getEndDate());
+ historyQuery.setSourceId(historyQueryDto.getSourceId());
+ historyQuery.setUserIdentity(historyQueryDto.getUserIdentity());
+ historyQuery.setOffset(historyQueryDto.getOffset());
+ historyQuery.setCount(historyQueryDto.getCount());
+ historyQuery.setSortColumn(historyQueryDto.getSortColumn());
+ historyQuery.setSortOrder(historyQueryDto.getSortOrder());
+
+ // perform the query
+ final History history = auditService.getActions(historyQuery);
+
+ // only retain authorized actions
+ final HistoryDTO historyDto = dtoFactory.createHistoryDto(history);
+ if (history.getActions() != null) {
+ final List<ActionEntity> actionEntities = new ArrayList<>();
+ for (final Action action : history.getActions()) {
+ final AuthorizationResult result = authorizeAction(action);
+ actionEntities.add(entityFactory.createActionEntity(dtoFactory.createActionDto(action), Result.Approved.equals(result.getResult())));
+ }
+ historyDto.setActions(actionEntities);
+ }
+
+ // create the response
+ return historyDto;
+ }
+
+ @Override
+ public ActionEntity getAction(final Integer actionId) {
+ // get the action
+ final Action action = auditService.getAction(actionId);
+
+ // ensure the action was found
+ if (action == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find action with id '%s'.", actionId));
+ }
+
+ final AuthorizationResult result = authorizeAction(action);
+ final boolean authorized = Result.Approved.equals(result.getResult());
+ if (!authorized) {
+ throw new AccessDeniedException(result.getExplanation());
+ }
+
+ // return the action
+ return entityFactory.createActionEntity(dtoFactory.createActionDto(action), authorized);
+ }
+
+ @Override
+ public ComponentHistoryDTO getComponentHistory(final String componentId) {
+ final Map<String, PropertyHistoryDTO> propertyHistoryDtos = new LinkedHashMap<>();
+ final Map<String, List<PreviousValue>> propertyHistory = auditService.getPreviousValues(componentId);
+
+ for (final Map.Entry<String, List<PreviousValue>> entry : propertyHistory.entrySet()) {
+ final List<PreviousValueDTO> previousValueDtos = new ArrayList<>();
+
+ for (final PreviousValue previousValue : entry.getValue()) {
+ final PreviousValueDTO dto = new PreviousValueDTO();
+ dto.setPreviousValue(previousValue.getPreviousValue());
+ dto.setTimestamp(previousValue.getTimestamp());
+ dto.setUserIdentity(previousValue.getUserIdentity());
+ previousValueDtos.add(dto);
+ }
+
+ if (!previousValueDtos.isEmpty()) {
+ final PropertyHistoryDTO propertyHistoryDto = new PropertyHistoryDTO();
+ propertyHistoryDto.setPreviousValues(previousValueDtos);
+ propertyHistoryDtos.put(entry.getKey(), propertyHistoryDto);
+ }
+ }
+
+ final ComponentHistoryDTO history = new ComponentHistoryDTO();
+ history.setComponentId(componentId);
+ history.setPropertyHistory(propertyHistoryDtos);
+
+ return history;
+ }
+
+ @Override
+ public ProcessorDiagnosticsEntity getProcessorDiagnostics(final String id) {
+ final ProcessorNode processor = processorDAO.getProcessor(id);
+ final ProcessorStatus processorStatus = controllerFacade.getProcessorStatus(id);
+
+ // Generate Processor Diagnostics
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final ProcessorDiagnosticsDTO dto = controllerFacade.getProcessorDiagnostics(processor, processorStatus, bulletinRepository, serviceId -> {
+ final ControllerServiceNode serviceNode = controllerServiceDAO.getControllerService(serviceId);
+ return createControllerServiceEntity(serviceNode, Collections.emptySet());
+ });
+
+ // Filter anything out of diagnostics that the user is not authorized to see.
+ final List<JVMDiagnosticsSnapshotDTO> jvmDiagnosticsSnaphots = new ArrayList<>();
+ final JVMDiagnosticsDTO jvmDiagnostics = dto.getJvmDiagnostics();
+ jvmDiagnosticsSnaphots.add(jvmDiagnostics.getAggregateSnapshot());
+
+ // filter controller-related information
+ final boolean canReadController = authorizableLookup.getController().isAuthorized(authorizer, RequestAction.READ, user);
+ if (!canReadController) {
+ for (final JVMDiagnosticsSnapshotDTO snapshot : jvmDiagnosticsSnaphots) {
+ snapshot.setControllerDiagnostics(null);
+ }
+ }
+
+ // filter system diagnostics information
+ final boolean canReadSystem = authorizableLookup.getSystem().isAuthorized(authorizer, RequestAction.READ, user);
+ if (!canReadSystem) {
+ for (final JVMDiagnosticsSnapshotDTO snapshot : jvmDiagnosticsSnaphots) {
+ snapshot.setSystemDiagnosticsDto(null);
+ }
+ }
+
+ final boolean canReadFlow = authorizableLookup.getFlow().isAuthorized(authorizer, RequestAction.READ, user);
+ if (!canReadFlow) {
+ for (final JVMDiagnosticsSnapshotDTO snapshot : jvmDiagnosticsSnaphots) {
+ snapshot.setFlowDiagnosticsDto(null);
+ }
+ }
+
+ // filter connections
+ final Predicate<ConnectionDiagnosticsDTO> connectionAuthorized = connectionDiagnostics -> {
+ final String connectionId = connectionDiagnostics.getConnection().getId();
+ return authorizableLookup.getConnection(connectionId).getAuthorizable().isAuthorized(authorizer, RequestAction.READ, user);
+ };
+
+ // Filter incoming connections by what user is authorized to READ
+ final Set<ConnectionDiagnosticsDTO> incoming = dto.getIncomingConnections();
+ final Set<ConnectionDiagnosticsDTO> filteredIncoming = incoming.stream()
+ .filter(connectionAuthorized)
+ .collect(Collectors.toSet());
+
+ dto.setIncomingConnections(filteredIncoming);
+
+ // Filter outgoing connections by what user is authorized to READ
+ final Set<ConnectionDiagnosticsDTO> outgoing = dto.getOutgoingConnections();
+ final Set<ConnectionDiagnosticsDTO> filteredOutgoing = outgoing.stream()
+ .filter(connectionAuthorized)
+ .collect(Collectors.toSet());
+ dto.setOutgoingConnections(filteredOutgoing);
+
+ // Filter out any controller services that are referenced by the Processor
+ final Set<ControllerServiceDiagnosticsDTO> referencedServices = dto.getReferencedControllerServices();
+ final Set<ControllerServiceDiagnosticsDTO> filteredReferencedServices = referencedServices.stream()
+ .filter(csDiagnostics -> {
+ final String csId = csDiagnostics.getControllerService().getId();
+ return authorizableLookup.getControllerService(csId).getAuthorizable().isAuthorized(authorizer, RequestAction.READ, user);
+ })
+ .map(csDiagnostics -> {
+ // Filter out any referencing components because those are generally not relevant from this context.
+ final ControllerServiceDTO serviceDto = csDiagnostics.getControllerService().getComponent();
+ if (serviceDto != null) {
+ serviceDto.setReferencingComponents(null);
+ }
+ return csDiagnostics;
+ })
+ .collect(Collectors.toSet());
+ dto.setReferencedControllerServices(filteredReferencedServices);
+
+ final Revision revision = revisionManager.getRevision(id);
+ final RevisionDTO revisionDto = dtoFactory.createRevisionDTO(revision);
+ final PermissionsDTO permissionsDto = dtoFactory.createPermissionsDto(processor);
+ final List<BulletinEntity> bulletins = bulletinRepository.findBulletinsForSource(id).stream()
+ .map(bulletin -> dtoFactory.createBulletinDto(bulletin))
+ .map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissionsDto.getCanRead()))
+ .collect(Collectors.toList());
+
+ final ProcessorStatusDTO processorStatusDto = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processor.getIdentifier()));
+ return entityFactory.createProcessorDiagnosticsEntity(dto, revisionDto, permissionsDto, processorStatusDto, bulletins);
+ }
+
+ @Override
+ public boolean isClustered() {
+ return controllerFacade.isClustered();
+ }
+
+ @Override
+ public String getNodeId() {
+ final NodeIdentifier nodeId = controllerFacade.getNodeId();
+ if (nodeId != null) {
+ return nodeId.getId();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ClusterDTO getCluster() {
+ // create cluster summary dto
+ final ClusterDTO clusterDto = new ClusterDTO();
+
+ // set current time
+ clusterDto.setGenerated(new Date());
+
+ // create node dtos
+ final List<NodeDTO> nodeDtos = clusterCoordinator.getNodeIdentifiers().stream()
+ .map(nodeId -> getNode(nodeId))
+ .collect(Collectors.toList());
+ clusterDto.setNodes(nodeDtos);
+
+ return clusterDto;
+ }
+
+ @Override
+ public NodeDTO getNode(final String nodeId) {
+ final NodeIdentifier nodeIdentifier = clusterCoordinator.getNodeIdentifier(nodeId);
+ return getNode(nodeIdentifier);
+ }
+
+ private NodeDTO getNode(final NodeIdentifier nodeId) {
+ final NodeConnectionStatus nodeStatus = clusterCoordinator.getConnectionStatus(nodeId);
+ final List<NodeEvent> events = clusterCoordinator.getNodeEvents(nodeId);
+ final Set<String> roles = getRoles(nodeId);
+ final NodeHeartbeat heartbeat = heartbeatMonitor.getLatestHeartbeat(nodeId);
+ return dtoFactory.createNodeDTO(nodeId, nodeStatus, heartbeat, events, roles);
+ }
+
+ private Set<String> getRoles(final NodeIdentifier nodeId) {
+ final Set<String> roles = new HashSet<>();
+ final String nodeAddress = nodeId.getSocketAddress() + ":" + nodeId.getSocketPort();
+
+ for (final String roleName : ClusterRoles.getAllRoles()) {
+ final String leader = leaderElectionManager.getLeader(roleName);
+ if (leader == null) {
+ continue;
+ }
+
+ if (leader.equals(nodeAddress)) {
+ roles.add(roleName);
+ }
+ }
+
+ return roles;
+ }
+
+ @Override
+ public void deleteNode(final String nodeId) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new WebApplicationException(new Throwable("Unable to access details for current user."));
+ }
+
+ final String userDn = user.getIdentity();
+ final NodeIdentifier nodeIdentifier = clusterCoordinator.getNodeIdentifier(nodeId);
+ if (nodeIdentifier == null) {
+ throw new UnknownNodeException("Cannot remove Node with ID " + nodeId + " because it is not part of the cluster");
+ }
+
+ final NodeConnectionStatus nodeConnectionStatus = clusterCoordinator.getConnectionStatus(nodeIdentifier);
+ if (!nodeConnectionStatus.getState().equals(NodeConnectionState.OFFLOADED) && !nodeConnectionStatus.getState().equals(NodeConnectionState.DISCONNECTED)) {
+ throw new IllegalNodeDeletionException("Cannot remove Node with ID " + nodeId +
+ " because it is not disconnected or offloaded, current state = " + nodeConnectionStatus.getState());
+ }
+
+ clusterCoordinator.removeNode(nodeIdentifier, userDn);
+ heartbeatMonitor.removeHeartbeat(nodeIdentifier);
+ }
+
+ /* reusable function declarations for converting ids to tenant entities */
+ private Function<String, TenantEntity> mapUserGroupIdToTenantEntity(final boolean enforceGroupExistence) {
+ return userGroupId -> {
+ final RevisionDTO userGroupRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(userGroupId));
+
+ final Group group;
+ if (enforceGroupExistence || userGroupDAO.hasUserGroup(userGroupId)) {
+ group = userGroupDAO.getUserGroup(userGroupId);
+ } else {
+ group = new Group.Builder().identifier(userGroupId).name("Group ID - " + userGroupId + " (removed externally)").build();
+ }
+
+ return entityFactory.createTenantEntity(dtoFactory.createTenantDTO(group), userGroupRevision,
+ dtoFactory.createPermissionsDto(authorizableLookup.getTenant()));
+ };
+ }
+
+ private Function<String, TenantEntity> mapUserIdToTenantEntity(final boolean enforceUserExistence) {
+ return userId -> {
+ final RevisionDTO userRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(userId));
+
+ final User user;
+ if (enforceUserExistence || userDAO.hasUser(userId)) {
+ user = userDAO.getUser(userId);
+ } else {
+ user = new User.Builder().identifier(userId).identity("User ID - " + userId + " (removed externally)").build();
+ }
+
+ return entityFactory.createTenantEntity(dtoFactory.createTenantDTO(user), userRevision,
+ dtoFactory.createPermissionsDto(authorizableLookup.getTenant()));
+ };
+ }
+
+
+ /* setters */
+ public void setProperties(final NiFiProperties properties) {
+ this.properties = properties;
+ }
+
+ public void setControllerFacade(final ControllerFacade controllerFacade) {
+ this.controllerFacade = controllerFacade;
+ }
+
+ public void setRemoteProcessGroupDAO(final RemoteProcessGroupDAO remoteProcessGroupDAO) {
+ this.remoteProcessGroupDAO = remoteProcessGroupDAO;
+ }
+
+ public void setLabelDAO(final LabelDAO labelDAO) {
+ this.labelDAO = labelDAO;
+ }
+
+ public void setFunnelDAO(final FunnelDAO funnelDAO) {
+ this.funnelDAO = funnelDAO;
+ }
+
+ public void setSnippetDAO(final SnippetDAO snippetDAO) {
+ this.snippetDAO = snippetDAO;
+ }
+
+ public void setProcessorDAO(final ProcessorDAO processorDAO) {
+ this.processorDAO = processorDAO;
+ }
+
+ public void setConnectionDAO(final ConnectionDAO connectionDAO) {
+ this.connectionDAO = connectionDAO;
+ }
+
+ public void setAuditService(final AuditService auditService) {
+ this.auditService = auditService;
+ }
+
+ public void setRevisionManager(final RevisionManager revisionManager) {
+ this.revisionManager = revisionManager;
+ }
+
+ public void setDtoFactory(final DtoFactory dtoFactory) {
+ this.dtoFactory = dtoFactory;
+ }
+
+ public void setEntityFactory(final EntityFactory entityFactory) {
+ this.entityFactory = entityFactory;
+ }
+
+ public void setInputPortDAO(final PortDAO inputPortDAO) {
+ this.inputPortDAO = inputPortDAO;
+ }
+
+ public void setOutputPortDAO(final PortDAO outputPortDAO) {
+ this.outputPortDAO = outputPortDAO;
+ }
+
+ public void setProcessGroupDAO(final ProcessGroupDAO processGroupDAO) {
+ this.processGroupDAO = processGroupDAO;
+ }
+
+ public void setControllerServiceDAO(final ControllerServiceDAO controllerServiceDAO) {
+ this.controllerServiceDAO = controllerServiceDAO;
+ }
+
+ public void setReportingTaskDAO(final ReportingTaskDAO reportingTaskDAO) {
+ this.reportingTaskDAO = reportingTaskDAO;
+ }
+
+ public void setTemplateDAO(final TemplateDAO templateDAO) {
+ this.templateDAO = templateDAO;
+ }
+
+ public void setSnippetUtils(final SnippetUtils snippetUtils) {
+ this.snippetUtils = snippetUtils;
+ }
+
+ public void setAuthorizableLookup(final AuthorizableLookup authorizableLookup) {
+ this.authorizableLookup = authorizableLookup;
+ }
+
+ public void setAuthorizer(final Authorizer authorizer) {
+ this.authorizer = authorizer;
+ }
+
+ public void setUserDAO(final UserDAO userDAO) {
+ this.userDAO = userDAO;
+ }
+
+ public void setUserGroupDAO(final UserGroupDAO userGroupDAO) {
+ this.userGroupDAO = userGroupDAO;
+ }
+
+ public void setAccessPolicyDAO(final AccessPolicyDAO accessPolicyDAO) {
+ this.accessPolicyDAO = accessPolicyDAO;
+ }
+
+ public void setClusterCoordinator(final ClusterCoordinator coordinator) {
+ this.clusterCoordinator = coordinator;
+ }
+
+ public void setHeartbeatMonitor(final HeartbeatMonitor heartbeatMonitor) {
+ this.heartbeatMonitor = heartbeatMonitor;
+ }
+
+ public void setBulletinRepository(final BulletinRepository bulletinRepository) {
+ this.bulletinRepository = bulletinRepository;
+ }
+
+ public void setLeaderElectionManager(final LeaderElectionManager leaderElectionManager) {
+ this.leaderElectionManager = leaderElectionManager;
+ }
+
+ public void setRegistryDAO(RegistryDAO registryDao) {
+ this.registryDAO = registryDao;
+ }
+
+ public void setFlowRegistryClient(FlowRegistryClient flowRegistryClient) {
+ this.flowRegistryClient = flowRegistryClient;
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
new file mode 100644
index 0000000..2943e10
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -0,0 +1,4354 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.web.api.dto;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.component.details.ComponentDetails;
+import org.apache.nifi.action.component.details.ExtensionDetails;
+import org.apache.nifi.action.component.details.FlowChangeExtensionDetails;
+import org.apache.nifi.action.component.details.FlowChangeRemoteProcessGroupDetails;
+import org.apache.nifi.action.component.details.RemoteProcessGroupDetails;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.ConfigureDetails;
+import org.apache.nifi.action.details.ConnectDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.action.details.FlowChangeConnectDetails;
+import org.apache.nifi.action.details.FlowChangeMoveDetails;
+import org.apache.nifi.action.details.FlowChangePurgeDetails;
+import org.apache.nifi.action.details.MoveDetails;
+import org.apache.nifi.action.details.PurgeDetails;
+import org.apache.nifi.annotation.behavior.Restricted;
+import org.apache.nifi.annotation.behavior.Restriction;
+import org.apache.nifi.annotation.behavior.Stateful;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.DeprecationNotice;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.ComponentAuthorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.cluster.coordination.heartbeat.NodeHeartbeat;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
+import org.apache.nifi.cluster.event.NodeEvent;
+import org.apache.nifi.cluster.manager.StatusMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateMap;
+import org.apache.nifi.components.validation.ValidationStatus;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.ConnectableType;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.Funnel;
+import org.apache.nifi.connectable.Port;
+import org.apache.nifi.connectable.Position;
+import org.apache.nifi.controller.ActiveThreadInfo;
+import org.apache.nifi.controller.ComponentNode;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.Counter;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.Snippet;
+import org.apache.nifi.controller.Template;
+import org.apache.nifi.controller.label.Label;
+import org.apache.nifi.controller.queue.DropFlowFileState;
+import org.apache.nifi.controller.queue.DropFlowFileStatus;
+import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.FlowFileSummary;
+import org.apache.nifi.controller.queue.ListFlowFileState;
+import org.apache.nifi.controller.queue.ListFlowFileStatus;
+import org.apache.nifi.controller.queue.LoadBalanceStrategy;
+import org.apache.nifi.controller.queue.LocalQueuePartitionDiagnostics;
+import org.apache.nifi.controller.queue.QueueDiagnostics;
+import org.apache.nifi.controller.queue.QueueSize;
+import org.apache.nifi.controller.queue.RemoteQueuePartitionDiagnostics;
+import org.apache.nifi.controller.repository.FlowFileRecord;
+import org.apache.nifi.controller.repository.claim.ContentClaim;
+import org.apache.nifi.controller.repository.claim.ResourceClaim;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceProvider;
+import org.apache.nifi.controller.state.SortedStateUtils;
+import org.apache.nifi.controller.status.ConnectionStatus;
+import org.apache.nifi.controller.status.PortStatus;
+import org.apache.nifi.controller.status.ProcessGroupStatus;
+import org.apache.nifi.controller.status.ProcessorStatus;
+import org.apache.nifi.controller.status.RemoteProcessGroupStatus;
+import org.apache.nifi.controller.status.history.GarbageCollectionHistory;
+import org.apache.nifi.controller.status.history.GarbageCollectionStatus;
+import org.apache.nifi.diagnostics.GarbageCollection;
+import org.apache.nifi.diagnostics.StorageUsage;
+import org.apache.nifi.diagnostics.SystemDiagnostics;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFilePrioritizer;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.groups.ProcessGroupCounts;
+import org.apache.nifi.groups.RemoteProcessGroup;
+import org.apache.nifi.groups.RemoteProcessGroupCounts;
+import org.apache.nifi.history.History;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarClassLoadersHolder;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.provenance.lineage.ComputeLineageResult;
+import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
+import org.apache.nifi.provenance.lineage.LineageEdge;
+import org.apache.nifi.provenance.lineage.LineageNode;
+import org.apache.nifi.provenance.lineage.ProvenanceEventLineageNode;
+import org.apache.nifi.registry.ComponentVariableRegistry;
+import org.apache.nifi.registry.flow.FlowRegistry;
+import org.apache.nifi.registry.flow.VersionControlInformation;
+import org.apache.nifi.registry.flow.VersionedComponent;
+import org.apache.nifi.registry.flow.VersionedFlowState;
+import org.apache.nifi.registry.flow.VersionedFlowStatus;
+import org.apache.nifi.registry.flow.diff.DifferenceType;
+import org.apache.nifi.registry.flow.diff.FlowComparison;
+import org.apache.nifi.registry.flow.diff.FlowDifference;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedComponent;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedConnection;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedControllerService;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedFunnel;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedLabel;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedPort;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessor;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedRemoteGroupPort;
+import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedRemoteProcessGroup;
+import org.apache.nifi.registry.variable.VariableRegistryUpdateRequest;
+import org.apache.nifi.registry.variable.VariableRegistryUpdateStep;
+import org.apache.nifi.remote.RemoteGroupPort;
+import org.apache.nifi.remote.RootGroupPort;
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.apache.nifi.util.FlowDifferenceFilters;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.web.FlowModification;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.api.dto.action.ActionDTO;
+import org.apache.nifi.web.api.dto.action.HistoryDTO;
+import org.apache.nifi.web.api.dto.action.component.details.ComponentDetailsDTO;
+import org.apache.nifi.web.api.dto.action.component.details.ExtensionDetailsDTO;
+import org.apache.nifi.web.api.dto.action.component.details.RemoteProcessGroupDetailsDTO;
+import org.apache.nifi.web.api.dto.action.details.ActionDetailsDTO;
+import org.apache.nifi.web.api.dto.action.details.ConfigureDetailsDTO;
+import org.apache.nifi.web.api.dto.action.details.ConnectDetailsDTO;
+import org.apache.nifi.web.api.dto.action.details.MoveDetailsDTO;
+import org.apache.nifi.web.api.dto.action.details.PurgeDetailsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ClassLoaderDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ConnectionDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ConnectionDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ControllerServiceDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.GCDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.GarbageCollectionDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMControllerDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMFlowDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.JVMSystemDiagnosticsSnapshotDTO;
+import org.apache.nifi.web.api.dto.diagnostics.LocalQueuePartitionDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ProcessorDiagnosticsDTO;
+import org.apache.nifi.web.api.dto.diagnostics.RemoteQueuePartitionDTO;
+import org.apache.nifi.web.api.dto.diagnostics.RepositoryUsageDTO;
+import org.apache.nifi.web.api.dto.diagnostics.ThreadDumpDTO;
+import org.apache.nifi.web.api.dto.flow.FlowBreadcrumbDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.LineageDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.LineageRequestDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.LineageRequestDTO.LineageRequestType;
+import org.apache.nifi.web.api.dto.provenance.lineage.LineageResultsDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.ProvenanceLinkDTO;
+import org.apache.nifi.web.api.dto.provenance.lineage.ProvenanceNodeDTO;
+import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
+import org.apache.nifi.web.api.dto.status.ConnectionStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.PortStatusDTO;
+import org.apache.nifi.web.api.dto.status.PortStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
+import org.apache.nifi.web.api.dto.status.ProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
+import org.apache.nifi.web.api.dto.status.ProcessorStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
+import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
+import org.apache.nifi.web.api.entity.AffectedComponentEntity;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.entity.BulletinEntity;
+import org.apache.nifi.web.api.entity.ComponentReferenceEntity;
+import org.apache.nifi.web.api.entity.ConnectionStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.FlowBreadcrumbEntity;
+import org.apache.nifi.web.api.entity.PortEntity;
+import org.apache.nifi.web.api.entity.PortStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.api.entity.ProcessorStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
+import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.VariableEntity;
+import org.apache.nifi.web.controller.ControllerFacade;
+import org.apache.nifi.web.revision.RevisionManager;
+
+import javax.ws.rs.WebApplicationException;
+import java.net.UnknownHostException;
+import java.text.Collator;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.net.InetAddress;
+
+
+public final class DtoFactory {
+
+ @SuppressWarnings("rawtypes")
+ private final static Comparator<Class> CLASS_NAME_COMPARATOR = new Comparator<Class>() {
+ @Override
+ public int compare(final Class class1, final Class class2) {
+ return Collator.getInstance(Locale.US).compare(class1.getSimpleName(), class2.getSimpleName());
+ }
+ };
+ public static final String SENSITIVE_VALUE_MASK = "********";
+
+ private BulletinRepository bulletinRepository;
+ private ControllerServiceProvider controllerServiceProvider;
+ private EntityFactory entityFactory;
+ private Authorizer authorizer;
+ private ExtensionManager extensionManager;
+
+ public ControllerConfigurationDTO createControllerConfigurationDto(final ControllerFacade controllerFacade) {
+ final ControllerConfigurationDTO dto = new ControllerConfigurationDTO();
+ dto.setMaxTimerDrivenThreadCount(controllerFacade.getMaxTimerDrivenThreadCount());
+ dto.setMaxEventDrivenThreadCount(controllerFacade.getMaxEventDrivenThreadCount());
+ return dto;
+ }
+
+ public FlowConfigurationDTO createFlowConfigurationDto(final String autoRefreshInterval,
+ final Long defaultBackPressureObjectThreshold,
+ final String defaultBackPressureDataSizeThreshold,
+ final String dcaeDistributorApiHostname) {
+ final FlowConfigurationDTO dto = new FlowConfigurationDTO();
+
+ // get the refresh interval
+ final long refreshInterval = FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.SECONDS);
+ dto.setAutoRefreshIntervalSeconds(refreshInterval);
+ dto.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer));
+ dto.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer));
+ dto.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer));
+
+ /* Renu - getting host IP */
+ dto.setDcaeDistributorApiHostname(dcaeDistributorApiHostname);
+
+ final Date now = new Date();
+ dto.setTimeOffset(TimeZone.getDefault().getOffset(now.getTime()));
+ dto.setCurrentTime(now);
+
+ dto.setDefaultBackPressureDataSizeThreshold(defaultBackPressureDataSizeThreshold);
+ dto.setDefaultBackPressureObjectThreshold(defaultBackPressureObjectThreshold);
+
+ return dto;
+ }
+
+ /**
+ * Creates an ActionDTO for the specified Action.
+ *
+ * @param action action
+ * @return dto
+ */
+ public ActionDTO createActionDto(final Action action) {
+ final ActionDTO actionDto = new ActionDTO();
+ actionDto.setId(action.getId());
+ actionDto.setSourceId(action.getSourceId());
+ actionDto.setSourceName(action.getSourceName());
+ actionDto.setSourceType(action.getSourceType().toString());
+ actionDto.setTimestamp(action.getTimestamp());
+ actionDto.setUserIdentity(action.getUserIdentity());
+ actionDto.setOperation(action.getOperation().toString());
+ actionDto.setActionDetails(createActionDetailsDto(action.getActionDetails()));
+ actionDto.setComponentDetails(createComponentDetailsDto(action.getComponentDetails()));
+
+ return actionDto;
+ }
+
+ /**
+ * Creates an ActionDetailsDTO for the specified ActionDetails.
+ *
+ * @param actionDetails details
+ * @return dto
+ */
+ private ActionDetailsDTO createActionDetailsDto(final ActionDetails actionDetails) {
+ if (actionDetails == null) {
+ return null;
+ }
+
+ if (actionDetails instanceof FlowChangeConfigureDetails) {
+ final ConfigureDetailsDTO configureDetails = new ConfigureDetailsDTO();
+ configureDetails.setName(((ConfigureDetails) actionDetails).getName());
+ configureDetails.setPreviousValue(((ConfigureDetails) actionDetails).getPreviousValue());
+ configureDetails.setValue(((ConfigureDetails) actionDetails).getValue());
+ return configureDetails;
+ } else if (actionDetails instanceof FlowChangeConnectDetails) {
+ final ConnectDetailsDTO connectDetails = new ConnectDetailsDTO();
+ connectDetails.setSourceId(((ConnectDetails) actionDetails).getSourceId());
+ connectDetails.setSourceName(((ConnectDetails) actionDetails).getSourceName());
+ connectDetails.setSourceType(((ConnectDetails) actionDetails).getSourceType().toString());
+ connectDetails.setRelationship(((ConnectDetails) actionDetails).getRelationship());
+ connectDetails.setDestinationId(((ConnectDetails) actionDetails).getDestinationId());
+ connectDetails.setDestinationName(((ConnectDetails) actionDetails).getDestinationName());
+ connectDetails.setDestinationType(((ConnectDetails) actionDetails).getDestinationType().toString());
+ return connectDetails;
+ } else if (actionDetails instanceof FlowChangeMoveDetails) {
+ final MoveDetailsDTO moveDetails = new MoveDetailsDTO();
+ moveDetails.setPreviousGroup(((MoveDetails) actionDetails).getPreviousGroup());
+ moveDetails.setPreviousGroupId(((MoveDetails) actionDetails).getPreviousGroupId());
+ moveDetails.setGroup(((MoveDetails) actionDetails).getGroup());
+ moveDetails.setGroupId(((MoveDetails) actionDetails).getGroupId());
+ return moveDetails;
+ } else if (actionDetails instanceof FlowChangePurgeDetails) {
+ final PurgeDetailsDTO purgeDetails = new PurgeDetailsDTO();
+ purgeDetails.setEndDate(((PurgeDetails) actionDetails).getEndDate());
+ return purgeDetails;
+ } else {
+ throw new WebApplicationException(new IllegalArgumentException(String.format("Unrecognized type of action details encountered %s during serialization.", actionDetails.toString())));
+ }
+ }
+
+ /**
+ * Creates a ComponentDetailsDTO for the specified ComponentDetails.
+ *
+ * @param componentDetails details
+ * @return dto
+ */
+ private ComponentDetailsDTO createComponentDetailsDto(final ComponentDetails componentDetails) {
+ if (componentDetails == null) {
+ return null;
+ }
+
+ if (componentDetails instanceof FlowChangeExtensionDetails) {
+ final ExtensionDetailsDTO processorDetails = new ExtensionDetailsDTO();
+ processorDetails.setType(((ExtensionDetails) componentDetails).getType());
+ return processorDetails;
+ } else if (componentDetails instanceof FlowChangeRemoteProcessGroupDetails) {
+ final RemoteProcessGroupDetailsDTO remoteProcessGroupDetails = new RemoteProcessGroupDetailsDTO();
+ remoteProcessGroupDetails.setUri(((RemoteProcessGroupDetails) componentDetails).getUri());
+ return remoteProcessGroupDetails;
+ } else {
+ throw new WebApplicationException(new IllegalArgumentException(String.format("Unrecognized type of component details encountered %s during serialization. ", componentDetails.toString())));
+ }
+ }
+
+ /**
+ * Creates a HistoryDTO from the specified History.
+ *
+ * @param history history
+ * @return dto
+ */
+ public HistoryDTO createHistoryDto(final History history) {
+ final HistoryDTO historyDto = new HistoryDTO();
+ historyDto.setTotal(history.getTotal());
+ historyDto.setLastRefreshed(history.getLastRefreshed());
+ return historyDto;
+ }
+
+ /**
+ * Creates a ComponentStateDTO for the given component and state's.
+ *
+ * @param componentId component id
+ * @param localState local state
+ * @param clusterState cluster state
+ * @return dto
+ */
+ public ComponentStateDTO createComponentStateDTO(final String componentId, final Class<?> componentClass, final StateMap localState, final StateMap clusterState) {
+ final ComponentStateDTO dto = new ComponentStateDTO();
+ dto.setComponentId(componentId);
+ dto.setStateDescription(getStateDescription(componentClass));
+ dto.setLocalState(createStateMapDTO(Scope.LOCAL, localState));
+ dto.setClusterState(createStateMapDTO(Scope.CLUSTER, clusterState));
+ return dto;
+ }
+
+ /**
+ * Gets the description of the state this component persists.
+ *
+ * @param componentClass the component class
+ * @return state description
+ */
+ private String getStateDescription(final Class<?> componentClass) {
+ final Stateful capabilityDesc = componentClass.getAnnotation(Stateful.class);
+ if (capabilityDesc != null) {
+ return capabilityDesc.description();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a StateMapDTO for the given scope and state map.
+ *
+ * @param scope the scope
+ * @param stateMap the state map
+ * @return dto
+ */
+ public StateMapDTO createStateMapDTO(final Scope scope, final StateMap stateMap) {
+ if (stateMap == null) {
+ return null;
+ }
+
+ final StateMapDTO dto = new StateMapDTO();
+ dto.setScope(scope.toString());
+
+ final TreeMap<String, String> sortedState = new TreeMap<>(SortedStateUtils.getKeyComparator());
+ final Map<String, String> state = stateMap.toMap();
+ sortedState.putAll(state);
+
+ int count = 0;
+ final List<StateEntryDTO> stateEntries = new ArrayList<>();
+ final Set<Map.Entry<String, String>> entrySet = sortedState.entrySet();
+ for (final Iterator<Entry<String, String>> iter = entrySet.iterator(); iter.hasNext() && count++ < SortedStateUtils.MAX_COMPONENT_STATE_ENTRIES;) {
+ final Map.Entry<String, String> entry = iter.next();
+ final StateEntryDTO entryDTO = new StateEntryDTO();
+ entryDTO.setKey(entry.getKey());
+ entryDTO.setValue(entry.getValue());
+ stateEntries.add(entryDTO);
+ }
+ dto.setTotalEntryCount(state.size());
+ dto.setState(stateEntries);
+
+ return dto;
+ }
+
+ /**
+ * Creates CounterDTOs for each Counter specified.
+ *
+ * @param counterDtos dtos
+ * @return dto
+ */
+ public CountersSnapshotDTO createCountersDto(final Collection<CounterDTO> counterDtos) {
+ final CountersSnapshotDTO dto = new CountersSnapshotDTO();
+ dto.setCounters(counterDtos);
+ dto.setGenerated(new Date());
+ return dto;
+ }
+
+ /**
+ * Creates a CounterDTO from the specified Counter.
+ *
+ * @param counter counter
+ * @return dto
+ */
+ public CounterDTO createCounterDto(final Counter counter) {
+ final CounterDTO dto = new CounterDTO();
+ dto.setId(counter.getIdentifier());
+ dto.setContext(counter.getContext());
+ dto.setName(counter.getName());
+ dto.setValueCount(counter.getValue());
+ dto.setValue(FormatUtils.formatCount(counter.getValue()));
+ return dto;
+ }
+
+ /**
+ * Creates a PositionDTO from the specified position
+ *
+ * @param position position
+ * @return dto
+ */
+ public PositionDTO createPositionDto(final Position position) {
+ return new PositionDTO(position.getX(), position.getY());
+ }
+
+ private boolean isDropRequestComplete(final DropFlowFileState state) {
+ return DropFlowFileState.COMPLETE.equals(state) || DropFlowFileState.CANCELED.equals(state) || DropFlowFileState.FAILURE.equals(state);
+ }
+
+ /**
+ * Creates a DropRequestDTO from the specified flow file status.
+ *
+ * @param dropRequest dropRequest
+ * @return dto
+ */
+ public DropRequestDTO createDropRequestDTO(final DropFlowFileStatus dropRequest) {
+ final DropRequestDTO dto = new DropRequestDTO();
+ dto.setId(dropRequest.getRequestIdentifier());
+ dto.setSubmissionTime(new Date(dropRequest.getRequestSubmissionTime()));
+ dto.setLastUpdated(new Date(dropRequest.getLastUpdated()));
+ dto.setState(dropRequest.getState().toString());
+ dto.setFailureReason(dropRequest.getFailureReason());
+ dto.setFinished(isDropRequestComplete(dropRequest.getState()));
+
+ final QueueSize dropped = dropRequest.getDroppedSize();
+ dto.setDroppedCount(dropped.getObjectCount());
+ dto.setDroppedSize(dropped.getByteCount());
+ dto.setDropped(FormatUtils.formatCount(dropped.getObjectCount()) + " / " + FormatUtils.formatDataSize(dropped.getByteCount()));
+
+ final QueueSize current = dropRequest.getCurrentSize();
+ dto.setCurrentCount(current.getObjectCount());
+ dto.setCurrentSize(current.getByteCount());
+ dto.setCurrent(FormatUtils.formatCount(current.getObjectCount()) + " / " + FormatUtils.formatDataSize(current.getByteCount()));
+
+ final QueueSize original = dropRequest.getOriginalSize();
+ dto.setOriginalCount(original.getObjectCount());
+ dto.setOriginalSize(original.getByteCount());
+ dto.setOriginal(FormatUtils.formatCount(original.getObjectCount()) + " / " + FormatUtils.formatDataSize(original.getByteCount()));
+
+ if (isDropRequestComplete(dropRequest.getState())) {
+ dto.setPercentCompleted(100);
+ } else {
+ dto.setPercentCompleted((dropped.getObjectCount() * 100) / original.getObjectCount());
+ }
+
+ return dto;
+ }
+
+ private boolean isListingRequestComplete(final ListFlowFileState state) {
+ return ListFlowFileState.COMPLETE.equals(state) || ListFlowFileState.CANCELED.equals(state) || ListFlowFileState.FAILURE.equals(state);
+ }
+
+ private QueueSizeDTO createQueueSizeDTO(final QueueSize queueSize) {
+ final QueueSizeDTO dto = new QueueSizeDTO();
+ dto.setByteCount(queueSize.getByteCount());
+ dto.setObjectCount(queueSize.getObjectCount());
+ return dto;
+ }
+
+ /**
+ * Creates a ListingRequestDTO from the specified ListFlowFileStatus.
+ *
+ * @param listingRequest listingRequest
+ * @return dto
+ */
+ public ListingRequestDTO createListingRequestDTO(final ListFlowFileStatus listingRequest) {
+ final ListingRequestDTO dto = new ListingRequestDTO();
+ dto.setId(listingRequest.getRequestIdentifier());
+ dto.setSubmissionTime(new Date(listingRequest.getRequestSubmissionTime()));
+ dto.setLastUpdated(new Date(listingRequest.getLastUpdated()));
+ dto.setState(listingRequest.getState().toString());
+ dto.setFailureReason(listingRequest.getFailureReason());
+ dto.setFinished(isListingRequestComplete(listingRequest.getState()));
+ dto.setMaxResults(listingRequest.getMaxResults());
+ dto.setPercentCompleted(listingRequest.getCompletionPercentage());
+
+ dto.setQueueSize(createQueueSizeDTO(listingRequest.getQueueSize()));
+
+ if (isListingRequestComplete(listingRequest.getState())) {
+ final List<FlowFileSummary> flowFileSummaries = listingRequest.getFlowFileSummaries();
+ if (flowFileSummaries != null) {
+ final Date now = new Date();
+ final List<FlowFileSummaryDTO> summaryDtos = new ArrayList<>(flowFileSummaries.size());
+ for (final FlowFileSummary summary : flowFileSummaries) {
+ summaryDtos.add(createFlowFileSummaryDTO(summary, now));
+ }
+ dto.setFlowFileSummaries(summaryDtos);
+ }
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a FlowFileSummaryDTO from the specified FlowFileSummary.
+ *
+ * @param summary summary
+ * @return dto
+ */
+ public FlowFileSummaryDTO createFlowFileSummaryDTO(final FlowFileSummary summary, final Date now) {
+ final FlowFileSummaryDTO dto = new FlowFileSummaryDTO();
+ dto.setUuid(summary.getUuid());
+ dto.setFilename(summary.getFilename());
+
+ dto.setPenalized(summary.isPenalized());
+ final long penaltyExpiration = summary.getPenaltyExpirationMillis() - now.getTime();
+ dto.setPenaltyExpiresIn(penaltyExpiration>=0?penaltyExpiration:0);
+
+ dto.setPosition(summary.getPosition());
+ dto.setSize(summary.getSize());
+
+ final long queuedDuration = now.getTime() - summary.getLastQueuedTime();
+ dto.setQueuedDuration(queuedDuration);
+
+ final long age = now.getTime() - summary.getLineageStartDate();
+ dto.setLineageDuration(age);
+
+ return dto;
+ }
+
+ /**
+ * Creates a FlowFileDTO from the specified FlowFileRecord.
+ *
+ * @param record record
+ * @return dto
+ */
+ public FlowFileDTO createFlowFileDTO(final FlowFileRecord record) {
+ final Date now = new Date();
+ final FlowFileDTO dto = new FlowFileDTO();
+ dto.setUuid(record.getAttribute(CoreAttributes.UUID.key()));
+ dto.setFilename(record.getAttribute(CoreAttributes.FILENAME.key()));
+
+ dto.setPenalized(record.isPenalized());
+ final long penaltyExpiration = record.getPenaltyExpirationMillis() - now.getTime();
+ dto.setPenaltyExpiresIn(penaltyExpiration>=0?penaltyExpiration:0);
+
+ dto.setSize(record.getSize());
+ dto.setAttributes(record.getAttributes());
+
+ final long queuedDuration = now.getTime() - record.getLastQueueDate();
+ dto.setQueuedDuration(queuedDuration);
+
+ final long age = now.getTime() - record.getLineageStartDate();
+ dto.setLineageDuration(age);
+
+ final ContentClaim contentClaim = record.getContentClaim();
+ if (contentClaim != null) {
+ final ResourceClaim resourceClaim = contentClaim.getResourceClaim();
+ dto.setContentClaimSection(resourceClaim.getSection());
+ dto.setContentClaimContainer(resourceClaim.getContainer());
+ dto.setContentClaimIdentifier(resourceClaim.getId());
+ dto.setContentClaimOffset(contentClaim.getOffset() + record.getContentClaimOffset());
+ dto.setContentClaimFileSizeBytes(record.getSize());
+ dto.setContentClaimFileSize(FormatUtils.formatDataSize(record.getSize()));
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a ConnectionDTO from the specified Connection.
+ *
+ * @param connection connection
+ * @return dto
+ */
+ public ConnectionDTO createConnectionDto(final Connection connection) {
+ if (connection == null) {
+ return null;
+ }
+
+ final ConnectionDTO dto = new ConnectionDTO();
+
+ dto.setId(connection.getIdentifier());
+ dto.setParentGroupId(connection.getProcessGroup().getIdentifier());
+
+ final List<PositionDTO> bendPoints = new ArrayList<>();
+ for (final Position bendPoint : connection.getBendPoints()) {
+ bendPoints.add(createPositionDto(bendPoint));
+ }
+ dto.setBends(bendPoints);
+ dto.setName(connection.getName());
+ dto.setLabelIndex(connection.getLabelIndex());
+ dto.setzIndex(connection.getZIndex());
+ dto.setSource(createConnectableDto(connection.getSource()));
+ dto.setDestination(createConnectableDto(connection.getDestination()));
+ dto.setVersionedComponentId(connection.getVersionedComponentId().orElse(null));
+
+ final FlowFileQueue flowFileQueue = connection.getFlowFileQueue();
+
+ dto.setBackPressureObjectThreshold(flowFileQueue.getBackPressureObjectThreshold());
+ dto.setBackPressureDataSizeThreshold(flowFileQueue.getBackPressureDataSizeThreshold());
+ dto.setFlowFileExpiration(flowFileQueue.getFlowFileExpiration());
+ dto.setPrioritizers(new ArrayList<String>());
+ for (final FlowFilePrioritizer comparator : flowFileQueue.getPriorities()) {
+ dto.getPrioritizers().add(comparator.getClass().getCanonicalName());
+ }
+
+ // For ports, we do not want to populate the relationships.
+ for (final Relationship selectedRelationship : connection.getRelationships()) {
+ if (!Relationship.ANONYMOUS.equals(selectedRelationship)) {
+ if (dto.getSelectedRelationships() == null) {
+ dto.setSelectedRelationships(new TreeSet<String>(Collator.getInstance(Locale.US)));
+ }
+
+ dto.getSelectedRelationships().add(selectedRelationship.getName());
+ }
+ }
+
+ // For ports, we do not want to populate the relationships.
+ for (final Relationship availableRelationship : connection.getSource().getRelationships()) {
+ if (!Relationship.ANONYMOUS.equals(availableRelationship)) {
+ if (dto.getAvailableRelationships() == null) {
+ dto.setAvailableRelationships(new TreeSet<String>(Collator.getInstance(Locale.US)));
+ }
+
+ dto.getAvailableRelationships().add(availableRelationship.getName());
+ }
+ }
+
+ final LoadBalanceStrategy loadBalanceStrategy = flowFileQueue.getLoadBalanceStrategy();
+ dto.setLoadBalancePartitionAttribute(flowFileQueue.getPartitioningAttribute());
+ dto.setLoadBalanceStrategy(loadBalanceStrategy.name());
+ dto.setLoadBalanceCompression(flowFileQueue.getLoadBalanceCompression().name());
+
+ if (loadBalanceStrategy == LoadBalanceStrategy.DO_NOT_LOAD_BALANCE) {
+ dto.setLoadBalanceStatus(ConnectionDTO.LOAD_BALANCE_NOT_CONFIGURED);
+ } else if (flowFileQueue.isActivelyLoadBalancing()) {
+ dto.setLoadBalanceStatus(ConnectionDTO.LOAD_BALANCE_ACTIVE);
+ } else {
+ dto.setLoadBalanceStatus(ConnectionDTO.LOAD_BALANCE_INACTIVE);
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a ConnectableDTO from the specified Connectable.
+ *
+ * @param connectable connectable
+ * @return dto
+ */
+ public ConnectableDTO createConnectableDto(final Connectable connectable) {
+ if (connectable == null) {
+ return null;
+ }
+
+ boolean isAuthorized = connectable.isAuthorized(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+
+ final ConnectableDTO dto = new ConnectableDTO();
+ dto.setId(connectable.getIdentifier());
+ dto.setName(isAuthorized ? connectable.getName() : connectable.getIdentifier());
+ dto.setType(connectable.getConnectableType().name());
+ dto.setVersionedComponentId(connectable.getVersionedComponentId().orElse(null));
+
+ if (connectable instanceof RemoteGroupPort) {
+ final RemoteGroupPort remoteGroupPort = (RemoteGroupPort) connectable;
+ final RemoteProcessGroup remoteGroup = remoteGroupPort.getRemoteProcessGroup();
+ dto.setGroupId(remoteGroup.getIdentifier());
+ dto.setRunning(remoteGroupPort.isTargetRunning());
+ dto.setTransmitting(remoteGroupPort.isRunning());
+ dto.setExists(remoteGroupPort.getTargetExists());
+ if (isAuthorized) {
+ dto.setComments(remoteGroup.getComments());
+ }
+ } else {
+ dto.setGroupId(connectable.getProcessGroup().getIdentifier());
+ dto.setRunning(connectable.isRunning());
+ if (isAuthorized) {
+ dto.setComments(connectable.getComments());
+ }
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a LabelDTO from the specified Label.
+ *
+ * @param label label
+ * @return dto
+ */
+ public LabelDTO createLabelDto(final Label label) {
+ if (label == null) {
+ return null;
+ }
+
+ final LabelDTO dto = new LabelDTO();
+ dto.setId(label.getIdentifier());
+ dto.setPosition(createPositionDto(label.getPosition()));
+ dto.setStyle(label.getStyle());
+ dto.setHeight(label.getSize().getHeight());
+ dto.setWidth(label.getSize().getWidth());
+ dto.setLabel(label.getValue());
+ dto.setParentGroupId(label.getProcessGroup().getIdentifier());
+ dto.setVersionedComponentId(label.getVersionedComponentId().orElse(null));
+
+ return dto;
+ }
+
+ /**
+ * Creates a {@link UserDTO} from the specified {@link User}.
+ *
+ * @param user user
+ * @return dto
+ */
+ public UserDTO createUserDto(final User user, final Set<TenantEntity> groups, final Set<AccessPolicySummaryEntity> accessPolicies) {
+ if (user == null) {
+ return null;
+ }
+
+ final UserDTO dto = new UserDTO();
+ dto.setId(user.getIdentifier());
+ dto.setUserGroups(groups);
+ dto.setIdentity(user.getIdentity());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isUserConfigurable(authorizer, user));
+ dto.setAccessPolicies(accessPolicies);
+
+ return dto;
+ }
+
+ /**
+ * Creates a {@link TenantDTO} from the specified {@link User}.
+ *
+ * @param user user
+ * @return dto
+ */
+ public TenantDTO createTenantDTO(User user) {
+ if (user == null) {
+ return null;
+ }
+
+ final TenantDTO dto = new TenantDTO();
+ dto.setId(user.getIdentifier());
+ dto.setIdentity(user.getIdentity());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isUserConfigurable(authorizer, user));
+
+ return dto;
+ }
+
+ /**
+ * Creates a {@link UserGroupDTO} from the specified {@link Group}.
+ *
+ * @param userGroup user group
+ * @return dto
+ */
+ public UserGroupDTO createUserGroupDto(final Group userGroup, Set<TenantEntity> users, final Set<AccessPolicySummaryEntity> accessPolicies) {
+ if (userGroup == null) {
+ return null;
+ }
+
+ // convert to access policies to handle backward compatibility due to incorrect
+ // type in the UserGroupDTO
+ final Set<AccessPolicyEntity> policies = accessPolicies.stream().map(summaryEntity -> {
+ final AccessPolicyDTO policy = new AccessPolicyDTO();
+ policy.setId(summaryEntity.getId());
+
+ if (summaryEntity.getPermissions().getCanRead()) {
+ final AccessPolicySummaryDTO summary = summaryEntity.getComponent();
+ policy.setResource(summary.getResource());
+ policy.setAction(summary.getAction());
+ policy.setConfigurable(summary.getConfigurable());
+ policy.setComponentReference(summary.getComponentReference());
+ }
+
+ return entityFactory.createAccessPolicyEntity(policy, summaryEntity.getRevision(), summaryEntity.getPermissions());
+ }).collect(Collectors.toSet());
+
+ final UserGroupDTO dto = new UserGroupDTO();
+ dto.setId(userGroup.getIdentifier());
+ dto.setUsers(users);
+ dto.setIdentity(userGroup.getName());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isGroupConfigurable(authorizer, userGroup));
+ dto.setAccessPolicies(policies);
+
+ return dto;
+ }
+
+ /**
+ * Creates a {@link TenantDTO} from the specified {@link User}.
+ *
+ * @param userGroup user
+ * @return dto
+ */
+ public TenantDTO createTenantDTO(Group userGroup) {
+ if (userGroup == null) {
+ return null;
+ }
+
+ final TenantDTO dto = new TenantDTO();
+ dto.setId(userGroup.getIdentifier());
+ dto.setIdentity(userGroup.getName());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isGroupConfigurable(authorizer, userGroup));
+
+ return dto;
+ }
+
+ /**
+ * Creates a FunnelDTO from the specified Funnel.
+ *
+ * @param funnel funnel
+ * @return dto
+ */
+ public FunnelDTO createFunnelDto(final Funnel funnel) {
+ if (funnel == null) {
+ return null;
+ }
+
+ final FunnelDTO dto = new FunnelDTO();
+ dto.setId(funnel.getIdentifier());
+ dto.setPosition(createPositionDto(funnel.getPosition()));
+ dto.setParentGroupId(funnel.getProcessGroup().getIdentifier());
+ dto.setVersionedComponentId(funnel.getVersionedComponentId().orElse(null));
+
+ return dto;
+ }
+
+ /**
+ * Creates a SnippetDTO from the specified Snippet.
+ *
+ * @param snippet snippet
+ * @return dto
+ */
+ public SnippetDTO createSnippetDto(final Snippet snippet) {
+ final SnippetDTO dto = new SnippetDTO();
+ dto.setId(snippet.getId());
+ dto.setParentGroupId(snippet.getParentGroupId());
+
+ // populate the snippet contents ids
+ dto.setConnections(mapRevisionToDto(snippet.getConnections()));
+ dto.setFunnels(mapRevisionToDto(snippet.getFunnels()));
+ dto.setInputPorts(mapRevisionToDto(snippet.getInputPorts()));
+ dto.setLabels(mapRevisionToDto(snippet.getLabels()));
+ dto.setOutputPorts(mapRevisionToDto(snippet.getOutputPorts()));
+ dto.setProcessGroups(mapRevisionToDto(snippet.getProcessGroups()));
+ dto.setProcessors(mapRevisionToDto(snippet.getProcessors()));
+ dto.setRemoteProcessGroups(mapRevisionToDto(snippet.getRemoteProcessGroups()));
+
+ return dto;
+ }
+
+ private Map<String, RevisionDTO> mapRevisionToDto(final Map<String, Revision> revisionMap) {
+ final Map<String, RevisionDTO> dtos = new HashMap<>(revisionMap.size());
+ for (final Map.Entry<String, Revision> entry : revisionMap.entrySet()) {
+ final Revision revision = entry.getValue();
+ final RevisionDTO revisionDto = new RevisionDTO();
+ revisionDto.setClientId(revision.getClientId());
+ revisionDto.setVersion(revision.getVersion());
+
+ dtos.put(entry.getKey(), revisionDto);
+ }
+ return dtos;
+ }
+
+ /**
+ * Creates a TemplateDTO from the specified template.
+ *
+ * @param template template
+ * @return dto
+ */
+ public TemplateDTO createTemplateDTO(final Template template) {
+ if (template == null) {
+ return null;
+ }
+
+ final TemplateDTO original = template.getDetails();
+
+ final TemplateDTO copy = new TemplateDTO();
+ copy.setId(original.getId());
+ copy.setGroupId(template.getProcessGroup().getIdentifier());
+ copy.setName(original.getName());
+ copy.setDescription(original.getDescription());
+ copy.setTimestamp(original.getTimestamp());
+ copy.setUri(original.getUri());
+ copy.setEncodingVersion(original.getEncodingVersion());
+
+ return copy;
+ }
+
+
+ public RemoteProcessGroupStatusDTO createRemoteProcessGroupStatusDto(final RemoteProcessGroup remoteProcessGroup, final RemoteProcessGroupStatus remoteProcessGroupStatus) {
+ final RemoteProcessGroupStatusDTO dto = new RemoteProcessGroupStatusDTO();
+ dto.setId(remoteProcessGroupStatus.getId());
+ dto.setGroupId(remoteProcessGroupStatus.getGroupId());
+ dto.setTargetUri(remoteProcessGroupStatus.getTargetUri());
+ dto.setName(remoteProcessGroupStatus.getName());
+ dto.setTransmissionStatus(remoteProcessGroupStatus.getTransmissionStatus().toString());
+ dto.setStatsLastRefreshed(new Date());
+ dto.setValidationStatus(getRemoteProcessGroupValidationStatus(remoteProcessGroup).name());
+
+ final RemoteProcessGroupStatusSnapshotDTO snapshot = new RemoteProcessGroupStatusSnapshotDTO();
+ dto.setAggregateSnapshot(snapshot);
+
+ snapshot.setId(remoteProcessGroupStatus.getId());
+ snapshot.setGroupId(remoteProcessGroupStatus.getGroupId());
+ snapshot.setName(remoteProcessGroupStatus.getName());
+ snapshot.setTargetUri(remoteProcessGroupStatus.getTargetUri());
+ snapshot.setTransmissionStatus(remoteProcessGroupStatus.getTransmissionStatus().toString());
+
+ snapshot.setActiveThreadCount(remoteProcessGroupStatus.getActiveThreadCount());
+ snapshot.setFlowFilesSent(remoteProcessGroupStatus.getSentCount());
+ snapshot.setBytesSent(remoteProcessGroupStatus.getSentContentSize());
+ snapshot.setFlowFilesReceived(remoteProcessGroupStatus.getReceivedCount());
+ snapshot.setBytesReceived(remoteProcessGroupStatus.getReceivedContentSize());
+
+ StatusMerger.updatePrettyPrintedFields(snapshot);
+ return dto;
+ }
+
+ private ValidationStatus getRemoteProcessGroupValidationStatus(RemoteProcessGroup remoteProcessGroup) {
+ final boolean hasAuthIssue = remoteProcessGroup.getAuthorizationIssue() != null && !remoteProcessGroup.getAuthorizationIssue().isEmpty();
+ final Collection<ValidationResult> validationResults = remoteProcessGroup.validate();
+ final boolean hasValidationIssue = validationResults != null && !validationResults.isEmpty();
+ return hasAuthIssue || hasValidationIssue ? ValidationStatus.INVALID : ValidationStatus.VALID;
+ }
+
+ public ProcessGroupStatusDTO createConciseProcessGroupStatusDto(final ProcessGroupStatus processGroupStatus) {
+ final ProcessGroupStatusDTO processGroupStatusDto = new ProcessGroupStatusDTO();
+ processGroupStatusDto.setId(processGroupStatus.getId());
+ processGroupStatusDto.setName(processGroupStatus.getName());
+ processGroupStatusDto.setStatsLastRefreshed(new Date());
+
+ final ProcessGroupStatusSnapshotDTO snapshot = new ProcessGroupStatusSnapshotDTO();
+ processGroupStatusDto.setAggregateSnapshot(snapshot);
+
+ snapshot.setId(processGroupStatus.getId());
+ snapshot.setName(processGroupStatus.getName());
+
+ if (processGroupStatus.getVersionedFlowState() != null) {
+ snapshot.setVersionedFlowState(processGroupStatus.getVersionedFlowState().name());
+ }
+
+ snapshot.setFlowFilesQueued(processGroupStatus.getQueuedCount());
+ snapshot.setBytesQueued(processGroupStatus.getQueuedContentSize());
+ snapshot.setBytesRead(processGroupStatus.getBytesRead());
+ snapshot.setBytesWritten(processGroupStatus.getBytesWritten());
+ snapshot.setFlowFilesIn(processGroupStatus.getInputCount());
+ snapshot.setBytesIn(processGroupStatus.getInputContentSize());
+ snapshot.setFlowFilesOut(processGroupStatus.getOutputCount());
+ snapshot.setBytesOut(processGroupStatus.getOutputContentSize());
+ snapshot.setFlowFilesTransferred(processGroupStatus.getFlowFilesTransferred());
+ snapshot.setBytesTransferred(processGroupStatus.getBytesTransferred());
+ snapshot.setFlowFilesSent(processGroupStatus.getFlowFilesSent());
+ snapshot.setBytesSent(processGroupStatus.getBytesSent());
+ snapshot.setFlowFilesReceived(processGroupStatus.getFlowFilesReceived());
+ snapshot.setBytesReceived(processGroupStatus.getBytesReceived());
+
+ snapshot.setActiveThreadCount(processGroupStatus.getActiveThreadCount());
+ snapshot.setTerminatedThreadCount(processGroupStatus.getTerminatedThreadCount());
+
+ StatusMerger.updatePrettyPrintedFields(snapshot);
+ return processGroupStatusDto;
+ }
+
+ public ProcessGroupStatusDTO createProcessGroupStatusDto(final ProcessGroup processGroup, final ProcessGroupStatus processGroupStatus) {
+ final ProcessGroupStatusDTO processGroupStatusDto = createConciseProcessGroupStatusDto(processGroupStatus);
+ final ProcessGroupStatusSnapshotDTO snapshot = processGroupStatusDto.getAggregateSnapshot();
+
+ // processor status
+ final Collection<ProcessorStatusSnapshotEntity> processorStatusSnapshotEntities = new ArrayList<>();
+ snapshot.setProcessorStatusSnapshots(processorStatusSnapshotEntities);
+ final Collection<ProcessorStatus> processorStatusCollection = processGroupStatus.getProcessorStatus();
+ if (processorStatusCollection != null) {
+ for (final ProcessorStatus processorStatus : processorStatusCollection) {
+ final ProcessorStatusDTO processorStatusDto = createProcessorStatusDto(processorStatus);
+ final ProcessorNode processor = processGroup.findProcessor(processorStatusDto.getId());
+ final PermissionsDTO processorPermissions = createPermissionsDto(processor);
+ processorStatusSnapshotEntities.add(entityFactory.createProcessorStatusSnapshotEntity(processorStatusDto.getAggregateSnapshot(), processorPermissions));
+ }
+ }
+
+ // connection status
+ final Collection<ConnectionStatusSnapshotEntity> connectionStatusDtoCollection = new ArrayList<>();
+ snapshot.setConnectionStatusSnapshots(connectionStatusDtoCollection);
+ final Collection<ConnectionStatus> connectionStatusCollection = processGroupStatus.getConnectionStatus();
+ if (connectionStatusCollection != null) {
+ for (final ConnectionStatus connectionStatus : connectionStatusCollection) {
+ final ConnectionStatusDTO connectionStatusDto = createConnectionStatusDto(connectionStatus);
+ final Connection connection = processGroup.findConnection(connectionStatusDto.getId());
+ final PermissionsDTO connectionPermissions = createPermissionsDto(connection);
+ connectionStatusDtoCollection.add(entityFactory.createConnectionStatusSnapshotEntity(connectionStatusDto.getAggregateSnapshot(), connectionPermissions));
+ }
+ }
+
+ // local child process groups
+ final Collection<ProcessGroupStatusSnapshotEntity> childProcessGroupStatusDtoCollection = new ArrayList<>();
+ snapshot.setProcessGroupStatusSnapshots(childProcessGroupStatusDtoCollection);
+ final Collection<ProcessGroupStatus> childProcessGroupStatusCollection = processGroupStatus.getProcessGroupStatus();
+ if (childProcessGroupStatusCollection != null) {
+ for (final ProcessGroupStatus childProcessGroupStatus : childProcessGroupStatusCollection) {
+ final ProcessGroupStatusDTO childProcessGroupStatusDto = createProcessGroupStatusDto(processGroup, childProcessGroupStatus);
+ final ProcessGroup childProcessGroup = processGroup.findProcessGroup(childProcessGroupStatusDto.getId());
+ final PermissionsDTO childProcessGroupPermissions = createPermissionsDto(childProcessGroup);
+ childProcessGroupStatusDtoCollection.add(entityFactory.createProcessGroupStatusSnapshotEntity(childProcessGroupStatusDto.getAggregateSnapshot(), childProcessGroupPermissions));
+ }
+ }
+
+ // remote child process groups
+ final Collection<RemoteProcessGroupStatusSnapshotEntity> childRemoteProcessGroupStatusDtoCollection = new ArrayList<>();
+ snapshot.setRemoteProcessGroupStatusSnapshots(childRemoteProcessGroupStatusDtoCollection);
+ final Collection<RemoteProcessGroupStatus> childRemoteProcessGroupStatusCollection = processGroupStatus.getRemoteProcessGroupStatus();
+ if (childRemoteProcessGroupStatusCollection != null) {
+ for (final RemoteProcessGroupStatus childRemoteProcessGroupStatus : childRemoteProcessGroupStatusCollection) {
+ final RemoteProcessGroup remoteProcessGroup = processGroup.findRemoteProcessGroup(childRemoteProcessGroupStatus.getId());
+ final RemoteProcessGroupStatusDTO childRemoteProcessGroupStatusDto = createRemoteProcessGroupStatusDto(remoteProcessGroup, childRemoteProcessGroupStatus);
+ final PermissionsDTO remoteProcessGroupPermissions = createPermissionsDto(remoteProcessGroup);
+ childRemoteProcessGroupStatusDtoCollection.add(entityFactory.createRemoteProcessGroupStatusSnapshotEntity(childRemoteProcessGroupStatusDto.getAggregateSnapshot(),
+ remoteProcessGroupPermissions));
+ }
+ }
+
+ // input ports
+ final Collection<PortStatusSnapshotEntity> inputPortStatusDtoCollection = new ArrayList<>();
+ snapshot.setInputPortStatusSnapshots(inputPortStatusDtoCollection);
+ final Collection<PortStatus> inputPortStatusCollection = processGroupStatus.getInputPortStatus();
+ if (inputPortStatusCollection != null) {
+ for (final PortStatus portStatus : inputPortStatusCollection) {
+ final PortStatusDTO portStatusDto = createPortStatusDto(portStatus);
+ final Port inputPort = processGroup.findInputPort(portStatus.getId());
+ final PermissionsDTO inputPortPermissions = createPermissionsDto(inputPort);
+ inputPortStatusDtoCollection.add(entityFactory.createPortStatusSnapshotEntity(portStatusDto.getAggregateSnapshot(), inputPortPermissions));
+ }
+ }
+
+ // output ports
+ final Collection<PortStatusSnapshotEntity> outputPortStatusDtoCollection = new ArrayList<>();
+ snapshot.setOutputPortStatusSnapshots(outputPortStatusDtoCollection);
+ final Collection<PortStatus> outputPortStatusCollection = processGroupStatus.getOutputPortStatus();
+ if (outputPortStatusCollection != null) {
+ for (final PortStatus portStatus : outputPortStatusCollection) {
+ final PortStatusDTO portStatusDto = createPortStatusDto(portStatus);
+ final Port outputPort = processGroup.findOutputPort(portStatus.getId());
+ final PermissionsDTO outputPortPermissions = createPermissionsDto(outputPort);
+ outputPortStatusDtoCollection.add(entityFactory.createPortStatusSnapshotEntity(portStatusDto.getAggregateSnapshot(), outputPortPermissions));
+ }
+ }
+
+ return processGroupStatusDto;
+ }
+
+ public ConnectionStatusDTO createConnectionStatusDto(final ConnectionStatus connectionStatus) {
+ final ConnectionStatusDTO connectionStatusDto = new ConnectionStatusDTO();
+ connectionStatusDto.setGroupId(connectionStatus.getGroupId());
+ connectionStatusDto.setId(connectionStatus.getId());
+ connectionStatusDto.setName(connectionStatus.getName());
+ connectionStatusDto.setSourceId(connectionStatus.getSourceId());
+ connectionStatusDto.setSourceName(connectionStatus.getSourceName());
+ connectionStatusDto.setDestinationId(connectionStatus.getDestinationId());
+ connectionStatusDto.setDestinationName(connectionStatus.getDestinationName());
+ connectionStatusDto.setStatsLastRefreshed(new Date());
+
+ final ConnectionStatusSnapshotDTO snapshot = new ConnectionStatusSnapshotDTO();
+ connectionStatusDto.setAggregateSnapshot(snapshot);
+
+ snapshot.setId(connectionStatus.getId());
+ snapshot.setGroupId(connectionStatus.getGroupId());
+ snapshot.setName(connectionStatus.getName());
+ snapshot.setSourceName(connectionStatus.getSourceName());
+ snapshot.setDestinationName(connectionStatus.getDestinationName());
+
+ snapshot.setFlowFilesQueued(connectionStatus.getQueuedCount());
+ snapshot.setBytesQueued(connectionStatus.getQueuedBytes());
+
+ snapshot.setFlowFilesIn(connectionStatus.getInputCount());
+ snapshot.setBytesIn(connectionStatus.getInputBytes());
+
+ snapshot.setFlowFilesOut(connectionStatus.getOutputCount());
+ snapshot.setBytesOut(connectionStatus.getOutputBytes());
+
+ if (connectionStatus.getBackPressureObjectThreshold() > 0) {
+ snapshot.setPercentUseCount(Math.min(100, StatusMerger.getUtilization(connectionStatus.getQueuedCount(), connectionStatus.getBackPressureObjectThreshold())));
+ }
+ if (connectionStatus.getBackPressureBytesThreshold() > 0) {
+ snapshot.setPercentUseBytes(Math.min(100, StatusMerger.getUtilization(connectionStatus.getQueuedBytes(), connectionStatus.getBackPressureBytesThreshold())));
+ }
+
+ StatusMerger.updatePrettyPrintedFields(snapshot);
+
+ return connectionStatusDto;
+ }
+
+ public ProcessorStatusDTO createProcessorStatusDto(final ProcessorStatus procStatus) {
+ final ProcessorStatusDTO dto = new ProcessorStatusDTO();
+ dto.setId(procStatus.getId());
+ dto.setGroupId(procStatus.getGroupId());
+ dto.setName(procStatus.getName());
+ dto.setStatsLastRefreshed(new Date());
+ dto.setRunStatus(procStatus.getRunStatus().toString());
+
+ final ProcessorStatusSnapshotDTO snapshot = new ProcessorStatusSnapshotDTO();
+ dto.setAggregateSnapshot(snapshot);
+
+ snapshot.setId(procStatus.getId());
+ snapshot.setGroupId(procStatus.getGroupId());
+ snapshot.setName(procStatus.getName());
+
+ snapshot.setFlowFilesOut(procStatus.getOutputCount());
+ snapshot.setBytesOut(procStatus.getOutputBytes());
+
+ snapshot.setFlowFilesIn(procStatus.getInputCount());
+ snapshot.setBytesIn(procStatus.getInputBytes());
+
+ snapshot.setBytesRead(procStatus.getBytesRead());
+ snapshot.setBytesWritten(procStatus.getBytesWritten());
+
+ snapshot.setTaskCount(procStatus.getInvocations());
+ snapshot.setTasksDurationNanos(procStatus.getProcessingNanos());
+ snapshot.setTasksDuration(FormatUtils.formatHoursMinutesSeconds(procStatus.getProcessingNanos(), TimeUnit.NANOSECONDS));
+
+ // determine the run status
+ snapshot.setRunStatus(procStatus.getRunStatus().toString());
+ snapshot.setExecutionNode(procStatus.getExecutionNode().toString());
+
+ snapshot.setActiveThreadCount(procStatus.getActiveThreadCount());
+ snapshot.setTerminatedThreadCount(procStatus.getTerminatedThreadCount());
+ snapshot.setType(procStatus.getType());
+
+ StatusMerger.updatePrettyPrintedFields(snapshot);
+ return dto;
+ }
+
+ /**
+ * Creates a PortStatusDTO for the specified PortStatus.
+ *
+ * @param portStatus status
+ * @return dto
+ */
+ public PortStatusDTO createPortStatusDto(final PortStatus portStatus) {
+ final PortStatusDTO dto = new PortStatusDTO();
+ dto.setId(portStatus.getId());
+ dto.setGroupId(portStatus.getGroupId());
+ dto.setName(portStatus.getName());
+ dto.setRunStatus(portStatus.getRunStatus().toString());
+ dto.setTransmitting(portStatus.isTransmitting());
+ dto.setStatsLastRefreshed(new Date());
+
+ final PortStatusSnapshotDTO snapshot = new PortStatusSnapshotDTO();
+ dto.setAggregateSnapshot(snapshot);
+
+ snapshot.setId(portStatus.getId());
+ snapshot.setGroupId(portStatus.getGroupId());
+ snapshot.setName(portStatus.getName());
+ snapshot.setRunStatus(portStatus.getRunStatus().toString());
+
+ snapshot.setActiveThreadCount(portStatus.getActiveThreadCount());
+ snapshot.setFlowFilesOut(portStatus.getOutputCount());
+ snapshot.setBytesOut(portStatus.getOutputBytes());
+
+ snapshot.setFlowFilesIn(portStatus.getInputCount());
+ snapshot.setBytesIn(portStatus.getInputBytes());
+ StatusMerger.updatePrettyPrintedFields(snapshot);
+
+ return dto;
+ }
+
+ /**
+ * Copies the specified snippet.
+ *
+ * @param originalSnippet snippet
+ * @return dto
+ */
+ public FlowSnippetDTO copySnippetContents(final FlowSnippetDTO originalSnippet) {
+ final FlowSnippetDTO copySnippet = new FlowSnippetDTO();
+
+ if (originalSnippet.getConnections() != null) {
+ for (final ConnectionDTO connection : originalSnippet.getConnections()) {
+ copySnippet.getConnections().add(copy(connection));
+ }
+ }
+ if (originalSnippet.getInputPorts() != null) {
+ for (final PortDTO port : originalSnippet.getInputPorts()) {
+ copySnippet.getInputPorts().add(copy(port));
+ }
+ }
+ if (originalSnippet.getOutputPorts() != null) {
+ for (final PortDTO port : originalSnippet.getOutputPorts()) {
+ copySnippet.getOutputPorts().add(copy(port));
+ }
+ }
+ if (originalSnippet.getProcessGroups() != null) {
+ for (final ProcessGroupDTO processGroup : originalSnippet.getProcessGroups()) {
+ copySnippet.getProcessGroups().add(copy(processGroup, true));
+ }
+ }
+ if (originalSnippet.getProcessors() != null) {
+ for (final ProcessorDTO processor : originalSnippet.getProcessors()) {
+ copySnippet.getProcessors().add(copy(processor));
+ }
+ }
+ if (originalSnippet.getLabels() != null) {
+ for (final LabelDTO label : originalSnippet.getLabels()) {
+ copySnippet.getLabels().add(copy(label));
+ }
+ }
+ if (originalSnippet.getFunnels() != null) {
+ for (final FunnelDTO funnel : originalSnippet.getFunnels()) {
+ copySnippet.getFunnels().add(copy(funnel));
+ }
+ }
+ if (originalSnippet.getRemoteProcessGroups() != null) {
+ for (final RemoteProcessGroupDTO remoteGroup : originalSnippet.getRemoteProcessGroups()) {
+ copySnippet.getRemoteProcessGroups().add(copy(remoteGroup));
+ }
+ }
+ if (originalSnippet.getControllerServices() != null) {
+ for (final ControllerServiceDTO controllerService : originalSnippet.getControllerServices()) {
+ copySnippet.getControllerServices().add(copy(controllerService));
+ }
+ }
+
+ return copySnippet;
+ }
+
+ /**
+ * Creates a PortDTO from the specified Port.
+ *
+ * @param port port
+ * @return dto
+ */
+ public PortDTO createPortDto(final Port port) {
+ if (port == null) {
+ return null;
+ }
+
+ final PortDTO dto = new PortDTO();
+ dto.setId(port.getIdentifier());
+ dto.setPosition(createPositionDto(port.getPosition()));
+ dto.setName(port.getName());
+ dto.setComments(port.getComments());
+ dto.setConcurrentlySchedulableTaskCount(port.getMaxConcurrentTasks());
+ dto.setParentGroupId(port.getProcessGroup().getIdentifier());
+ dto.setState(port.getScheduledState().toString());
+ dto.setType(port.getConnectableType().name());
+ dto.setVersionedComponentId(port.getVersionedComponentId().orElse(null));
+
+ // if this port is on the root group, determine if its actually connected to another nifi
+ if (port instanceof RootGroupPort) {
+ final RootGroupPort rootGroupPort = (RootGroupPort) port;
+ dto.setTransmitting(rootGroupPort.isTransmitting());
+ dto.setGroupAccessControl(rootGroupPort.getGroupAccessControl());
+ dto.setUserAccessControl(rootGroupPort.getUserAccessControl());
+ }
+
+ final Collection<ValidationResult> validationErrors = port.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ public ReportingTaskDTO createReportingTaskDto(final ReportingTaskNode reportingTaskNode) {
+ final BundleCoordinate bundleCoordinate = reportingTaskNode.getBundleCoordinate();
+ final List<Bundle> compatibleBundles = extensionManager.getBundles(reportingTaskNode.getCanonicalClassName()).stream().filter(bundle -> {
+ final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate();
+ return bundleCoordinate.getGroup().equals(coordinate.getGroup()) && bundleCoordinate.getId().equals(coordinate.getId());
+ }).collect(Collectors.toList());
+
+ final ReportingTaskDTO dto = new ReportingTaskDTO();
+ dto.setId(reportingTaskNode.getIdentifier());
+ dto.setName(reportingTaskNode.getName());
+ dto.setType(reportingTaskNode.getCanonicalClassName());
+ dto.setBundle(createBundleDto(bundleCoordinate));
+ dto.setSchedulingStrategy(reportingTaskNode.getSchedulingStrategy().name());
+ dto.setSchedulingPeriod(reportingTaskNode.getSchedulingPeriod());
+ dto.setState(reportingTaskNode.getScheduledState().name());
+ dto.setActiveThreadCount(reportingTaskNode.getActiveThreadCount());
+ dto.setAnnotationData(reportingTaskNode.getAnnotationData());
+ dto.setComments(reportingTaskNode.getComments());
+ dto.setPersistsState(reportingTaskNode.getReportingTask().getClass().isAnnotationPresent(Stateful.class));
+ dto.setRestricted(reportingTaskNode.isRestricted());
+ dto.setDeprecated(reportingTaskNode.isDeprecated());
+ dto.setExtensionMissing(reportingTaskNode.isExtensionMissing());
+ dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1);
+
+ final Map<String, String> defaultSchedulingPeriod = new HashMap<>();
+ defaultSchedulingPeriod.put(SchedulingStrategy.TIMER_DRIVEN.name(), SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+ defaultSchedulingPeriod.put(SchedulingStrategy.CRON_DRIVEN.name(), SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+ dto.setDefaultSchedulingPeriod(defaultSchedulingPeriod);
+
+ // sort a copy of the properties
+ final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+ @Override
+ public int compare(final PropertyDescriptor o1, final PropertyDescriptor o2) {
+ return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+ }
+ });
+ sortedProperties.putAll(reportingTaskNode.getProperties());
+
+ // get the property order from the reporting task
+ final ReportingTask reportingTask = reportingTaskNode.getReportingTask();
+ final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+ final List<PropertyDescriptor> descriptors = reportingTask.getPropertyDescriptors();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ for (final PropertyDescriptor descriptor : descriptors) {
+ orderedProperties.put(descriptor, null);
+ }
+ }
+ orderedProperties.putAll(sortedProperties);
+
+ // build the descriptor and property dtos
+ dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+ dto.setProperties(new LinkedHashMap<String, String>());
+ for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+
+ // store the property descriptor
+ dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor, null));
+
+ // determine the property value - don't include sensitive properties
+ String propertyValue = entry.getValue();
+ if (propertyValue != null && descriptor.isSensitive()) {
+ propertyValue = SENSITIVE_VALUE_MASK;
+ }
+
+ // set the property value
+ dto.getProperties().put(descriptor.getName(), propertyValue);
+ }
+
+ final ValidationStatus validationStatus = reportingTaskNode.getValidationStatus(1, TimeUnit.MILLISECONDS);
+ dto.setValidationStatus(validationStatus.name());
+
+ // add the validation errors
+ final Collection<ValidationResult> validationErrors = reportingTaskNode.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ public ControllerServiceDTO createControllerServiceDto(final ControllerServiceNode controllerServiceNode) {
+ final BundleCoordinate bundleCoordinate = controllerServiceNode.getBundleCoordinate();
+ final List<Bundle> compatibleBundles = extensionManager.getBundles(controllerServiceNode.getCanonicalClassName()).stream().filter(bundle -> {
+ final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate();
+ return bundleCoordinate.getGroup().equals(coordinate.getGroup()) && bundleCoordinate.getId().equals(coordinate.getId());
+ }).collect(Collectors.toList());
+
+ final ControllerServiceDTO dto = new ControllerServiceDTO();
+ dto.setId(controllerServiceNode.getIdentifier());
+ dto.setParentGroupId(controllerServiceNode.getProcessGroup() == null ? null : controllerServiceNode.getProcessGroup().getIdentifier());
+ dto.setName(controllerServiceNode.getName());
+ dto.setType(controllerServiceNode.getCanonicalClassName());
+ dto.setBundle(createBundleDto(bundleCoordinate));
+ dto.setControllerServiceApis(createControllerServiceApiDto(controllerServiceNode.getControllerServiceImplementation().getClass()));
+ dto.setState(controllerServiceNode.getState().name());
+ dto.setAnnotationData(controllerServiceNode.getAnnotationData());
+ dto.setComments(controllerServiceNode.getComments());
+ dto.setPersistsState(controllerServiceNode.getControllerServiceImplementation().getClass().isAnnotationPresent(Stateful.class));
+ dto.setRestricted(controllerServiceNode.isRestricted());
+ dto.setDeprecated(controllerServiceNode.isDeprecated());
+ dto.setExtensionMissing(controllerServiceNode.isExtensionMissing());
+ dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1);
+ dto.setVersionedComponentId(controllerServiceNode.getVersionedComponentId().orElse(null));
+
+ // sort a copy of the properties
+ final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+ @Override
+ public int compare(final PropertyDescriptor o1, final PropertyDescriptor o2) {
+ return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+ }
+ });
+ sortedProperties.putAll(controllerServiceNode.getProperties());
+
+ // get the property order from the controller service
+ final ControllerService controllerService = controllerServiceNode.getControllerServiceImplementation();
+ final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+ final List<PropertyDescriptor> descriptors = controllerService.getPropertyDescriptors();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ for (final PropertyDescriptor descriptor : descriptors) {
+ orderedProperties.put(descriptor, null);
+ }
+ }
+ orderedProperties.putAll(sortedProperties);
+
+ // build the descriptor and property dtos
+ dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+ dto.setProperties(new LinkedHashMap<String, String>());
+ for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+
+ // store the property descriptor
+ final String groupId = controllerServiceNode.getProcessGroup() == null ? null : controllerServiceNode.getProcessGroup().getIdentifier();
+ dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor, groupId));
+
+ // determine the property value - don't include sensitive properties
+ String propertyValue = entry.getValue();
+ if (propertyValue != null && descriptor.isSensitive()) {
+ propertyValue = SENSITIVE_VALUE_MASK;
+ }
+
+ // set the property value
+ dto.getProperties().put(descriptor.getName(), propertyValue);
+ }
+
+ dto.setValidationStatus(controllerServiceNode.getValidationStatus(1, TimeUnit.MILLISECONDS).name());
+
+ // add the validation errors
+ final Collection<ValidationResult> validationErrors = controllerServiceNode.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ public ControllerServiceReferencingComponentDTO createControllerServiceReferencingComponentDTO(final ComponentNode component) {
+ final ControllerServiceReferencingComponentDTO dto = new ControllerServiceReferencingComponentDTO();
+ dto.setId(component.getIdentifier());
+ dto.setName(component.getName());
+
+ String processGroupId = null;
+ List<PropertyDescriptor> propertyDescriptors = null;
+ Collection<ValidationResult> validationErrors = null;
+ if (component instanceof ProcessorNode) {
+ final ProcessorNode node = ((ProcessorNode) component);
+ dto.setGroupId(node.getProcessGroup().getIdentifier());
+ dto.setState(node.getScheduledState().name());
+ dto.setActiveThreadCount(node.getActiveThreadCount());
+ dto.setType(node.getComponentType());
+ dto.setReferenceType(Processor.class.getSimpleName());
+
+ propertyDescriptors = node.getProcessor().getPropertyDescriptors();
+ validationErrors = node.getValidationErrors();
+ processGroupId = node.getProcessGroup().getIdentifier();
+ } else if (component instanceof ControllerServiceNode) {
+ final ControllerServiceNode node = ((ControllerServiceNode) component);
+ dto.setState(node.getState().name());
+ dto.setType(node.getComponentType());
+ dto.setReferenceType(ControllerService.class.getSimpleName());
+
+ propertyDescriptors = node.getControllerServiceImplementation().getPropertyDescriptors();
+ validationErrors = node.getValidationErrors();
+ processGroupId = node.getProcessGroup() == null ? null : node.getProcessGroup().getIdentifier();
+ } else if (component instanceof ReportingTaskNode) {
+ final ReportingTaskNode node = ((ReportingTaskNode) component);
+ dto.setState(node.getScheduledState().name());
+ dto.setActiveThreadCount(node.getActiveThreadCount());
+ dto.setType(node.getComponentType());
+ dto.setReferenceType(ReportingTask.class.getSimpleName());
+
+ propertyDescriptors = node.getReportingTask().getPropertyDescriptors();
+ validationErrors = node.getValidationErrors();
+ processGroupId = null;
+ }
+
+ // ensure descriptors is non null
+ if (propertyDescriptors == null) {
+ propertyDescriptors = new ArrayList<>();
+ }
+
+ // process properties unconditionally since dynamic properties are available here and not in getPropertyDescriptors
+ final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+ @Override
+ public int compare(final PropertyDescriptor o1, final PropertyDescriptor o2) {
+ return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+ }
+ });
+ sortedProperties.putAll(component.getProperties());
+
+ final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+ for (final PropertyDescriptor descriptor : propertyDescriptors) {
+ orderedProperties.put(descriptor, null);
+ }
+ orderedProperties.putAll(sortedProperties);
+
+ // build the descriptor and property dtos
+ dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+ dto.setProperties(new LinkedHashMap<String, String>());
+ for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+
+ // store the property descriptor
+ dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor, processGroupId));
+
+ // determine the property value - don't include sensitive properties
+ String propertyValue = entry.getValue();
+ if (propertyValue != null && descriptor.isSensitive()) {
+ propertyValue = SENSITIVE_VALUE_MASK;
+ }
+
+ // set the property value
+ dto.getProperties().put(descriptor.getName(), propertyValue);
+ }
+
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ public RemoteProcessGroupPortDTO createRemoteProcessGroupPortDto(final RemoteGroupPort port) {
+ if (port == null) {
+ return null;
+ }
+
+ final RemoteProcessGroupPortDTO dto = new RemoteProcessGroupPortDTO();
+ dto.setId(port.getIdentifier());
+ dto.setGroupId(port.getRemoteProcessGroup().getIdentifier());
+ dto.setTargetId(port.getTargetIdentifier());
+ dto.setName(port.getName());
+ dto.setComments(port.getComments());
+ dto.setTransmitting(port.isRunning());
+ dto.setTargetRunning(port.isTargetRunning());
+ dto.setConcurrentlySchedulableTaskCount(port.getMaxConcurrentTasks());
+ dto.setUseCompression(port.isUseCompression());
+ dto.setExists(port.getTargetExists());
+ dto.setVersionedComponentId(port.getVersionedComponentId().orElse(null));
+
+ final BatchSettingsDTO batchDTO = new BatchSettingsDTO();
+ batchDTO.setCount(port.getBatchCount());
+ batchDTO.setSize(port.getBatchSize());
+ batchDTO.setDuration(port.getBatchDuration());
+ dto.setBatchSettings(batchDTO);
+
+ // determine if this port is currently connected to another component locally
+ if (ConnectableType.REMOTE_OUTPUT_PORT.equals(port.getConnectableType())) {
+ dto.setConnected(!port.getConnections().isEmpty());
+ } else {
+ dto.setConnected(port.hasIncomingConnection());
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a RemoteProcessGroupDTO from the specified RemoteProcessGroup.
+ *
+ * @param group group
+ * @return dto
+ */
+ public RemoteProcessGroupDTO createRemoteProcessGroupDto(final RemoteProcessGroup group) {
+ if (group == null) {
+ return null;
+ }
+
+ final Set<RemoteProcessGroupPortDTO> inputPorts = new HashSet<>();
+ final Set<RemoteProcessGroupPortDTO> outputPorts = new HashSet<>();
+
+ int activeRemoteInputPortCount = 0;
+ int inactiveRemoteInputPortCount = 0;
+ for (final Port port : group.getInputPorts()) {
+ inputPorts.add(createRemoteProcessGroupPortDto((RemoteGroupPort) port));
+
+ if (port.hasIncomingConnection()) {
+ if (port.isRunning()) {
+ activeRemoteInputPortCount++;
+ } else {
+ inactiveRemoteInputPortCount++;
+ }
+ }
+ }
+
+ int activeRemoteOutputPortCount = 0;
+ int inactiveRemoteOutputPortCount = 0;
+ for (final Port port : group.getOutputPorts()) {
+ outputPorts.add(createRemoteProcessGroupPortDto((RemoteGroupPort) port));
+
+ if (!port.getConnections().isEmpty()) {
+ if (port.isRunning()) {
+ activeRemoteOutputPortCount++;
+ } else {
+ inactiveRemoteOutputPortCount++;
+ }
+ }
+ }
+
+ final RemoteProcessGroupContentsDTO contents = new RemoteProcessGroupContentsDTO();
+ contents.setInputPorts(inputPorts);
+ contents.setOutputPorts(outputPorts);
+
+ final RemoteProcessGroupDTO dto = new RemoteProcessGroupDTO();
+ dto.setId(group.getIdentifier());
+ dto.setName(group.getName());
+ dto.setPosition(createPositionDto(group.getPosition()));
+ dto.setComments(group.getComments());
+ dto.setTransmitting(group.isTransmitting());
+ dto.setCommunicationsTimeout(group.getCommunicationsTimeout());
+ dto.setYieldDuration(group.getYieldDuration());
+ dto.setParentGroupId(group.getProcessGroup().getIdentifier());
+ dto.setTargetUris(group.getTargetUris());
+ dto.setFlowRefreshed(group.getLastRefreshTime());
+ dto.setContents(contents);
+ dto.setTransportProtocol(group.getTransportProtocol().name());
+ dto.setProxyHost(group.getProxyHost());
+ dto.setProxyPort(group.getProxyPort());
+ dto.setProxyUser(group.getProxyUser());
+ if (!StringUtils.isEmpty(group.getProxyPassword())) {
+ dto.setProxyPassword(SENSITIVE_VALUE_MASK);
+ }
+
+ // only specify the secure flag if we know the target system has site to site enabled
+ if (group.isSiteToSiteEnabled()) {
+ dto.setTargetSecure(group.getSecureFlag());
+ }
+
+ if (group.getAuthorizationIssue() != null) {
+ dto.setAuthorizationIssues(Arrays.asList(group.getAuthorizationIssue()));
+ }
+
+ final Collection<ValidationResult> validationErrors = group.validate();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ dto.setLocalNetworkInterface(group.getNetworkInterface());
+
+ dto.setActiveRemoteInputPortCount(activeRemoteInputPortCount);
+ dto.setInactiveRemoteInputPortCount(inactiveRemoteInputPortCount);
+ dto.setActiveRemoteOutputPortCount(activeRemoteOutputPortCount);
+ dto.setInactiveRemoteOutputPortCount(inactiveRemoteOutputPortCount);
+ dto.setVersionedComponentId(group.getVersionedComponentId().orElse(null));
+
+ final RemoteProcessGroupCounts counts = group.getCounts();
+ if (counts != null) {
+ dto.setInputPortCount(counts.getInputPortCount());
+ dto.setOutputPortCount(counts.getOutputPortCount());
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a FlowBreadcrumbEntity from the specified parent ProcessGroup.
+ *
+ * @param group group
+ * @return dto
+ */
+ private FlowBreadcrumbEntity createBreadcrumbEntity(final ProcessGroup group) {
+ if (group == null) {
+ return null;
+ }
+
+ final FlowBreadcrumbDTO dto = createBreadcrumbDto(group);
+ final PermissionsDTO permissions = createPermissionsDto(group);
+ final FlowBreadcrumbEntity entity = entityFactory.createFlowBreadcrumbEntity(dto, permissions);
+
+ if (group.getParent() != null) {
+ entity.setParentBreadcrumb(createBreadcrumbEntity(group.getParent()));
+ }
+
+ return entity;
+ }
+
+ /**
+ * Creates a FlowBreadcrumbDTO from the specified parent ProcessGroup.
+ *
+ * @param group group
+ * @return dto
+ */
+ private FlowBreadcrumbDTO createBreadcrumbDto(final ProcessGroup group) {
+ if (group == null) {
+ return null;
+ }
+
+ final FlowBreadcrumbDTO dto = new FlowBreadcrumbDTO();
+ dto.setId(group.getIdentifier());
+ dto.setName(group.getName());
+
+ final VersionControlInformationDTO versionControlInformation = createVersionControlInformationDto(group);
+ dto.setVersionControlInformation(versionControlInformation);
+
+ return dto;
+ }
+
+ public ComponentReferenceDTO createComponentReferenceDto(final Authorizable authorizable) {
+ if (authorizable == null || !(authorizable instanceof ComponentAuthorizable)) {
+ return null;
+ }
+
+ final ComponentAuthorizable componentAuthorizable = (ComponentAuthorizable) authorizable;
+ final ComponentReferenceDTO dto = new ComponentReferenceDTO();
+ dto.setId(componentAuthorizable.getIdentifier());
+ dto.setParentGroupId(componentAuthorizable.getProcessGroupIdentifier());
+ dto.setName(authorizable.getResource().getName());
+
+ return dto;
+ }
+
+ public AccessPolicySummaryDTO createAccessPolicySummaryDto(final AccessPolicy accessPolicy, final ComponentReferenceEntity componentReference) {
+ if (accessPolicy == null) {
+ return null;
+ }
+
+ final AccessPolicySummaryDTO dto = new AccessPolicySummaryDTO();
+ dto.setId(accessPolicy.getIdentifier());
+ dto.setResource(accessPolicy.getResource());
+ dto.setAction(accessPolicy.getAction().toString());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy));
+ dto.setComponentReference(componentReference);
+ return dto;
+ }
+
+ public AccessPolicyDTO createAccessPolicyDto(final AccessPolicy accessPolicy, final Set<TenantEntity> userGroups,
+ final Set<TenantEntity> users, final ComponentReferenceEntity componentReference) {
+
+ if (accessPolicy == null) {
+ return null;
+ }
+
+ final AccessPolicyDTO dto = new AccessPolicyDTO();
+ dto.setUserGroups(userGroups);
+ dto.setUsers(users);
+ dto.setId(accessPolicy.getIdentifier());
+ dto.setResource(accessPolicy.getResource());
+ dto.setAction(accessPolicy.getAction().toString());
+ dto.setConfigurable(AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy));
+ dto.setComponentReference(componentReference);
+ return dto;
+ }
+
+ /**
+ * Creates the PermissionsDTO based on the specified Authorizable.
+ *
+ * @param authorizable authorizable
+ * @return dto
+ */
+ public PermissionsDTO createPermissionsDto(final Authorizable authorizable) {
+ return createPermissionsDto(authorizable, NiFiUserUtils.getNiFiUser());
+ }
+
+ /**
+ * Creates the PermissionsDTO based on the specified Authorizable for the given user
+ *
+ * @param authorizable authorizable
+ * @param user the NiFi User for which the Permissions are being created
+ * @return dto
+ */
+ public PermissionsDTO createPermissionsDto(final Authorizable authorizable, final NiFiUser user) {
+ final PermissionsDTO dto = new PermissionsDTO();
+ dto.setCanRead(authorizable.isAuthorized(authorizer, RequestAction.READ, user));
+ dto.setCanWrite(authorizable.isAuthorized(authorizer, RequestAction.WRITE, user));
+ return dto;
+ }
+
+ public AffectedComponentEntity createAffectedComponentEntity(final ProcessorEntity processorEntity) {
+ if (processorEntity == null) {
+ return null;
+ }
+
+ final AffectedComponentEntity component = new AffectedComponentEntity();
+ component.setBulletins(processorEntity.getBulletins());
+ component.setId(processorEntity.getId());
+ component.setPermissions(processorEntity.getPermissions());
+ component.setPosition(processorEntity.getPosition());
+ component.setRevision(processorEntity.getRevision());
+ component.setUri(processorEntity.getUri());
+
+ final ProcessorDTO processorDto = processorEntity.getComponent();
+ final AffectedComponentDTO componentDto = new AffectedComponentDTO();
+ componentDto.setId(processorDto.getId());
+ componentDto.setName(processorDto.getName());
+ componentDto.setProcessGroupId(processorDto.getParentGroupId());
+ componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_PROCESSOR);
+ componentDto.setState(processorDto.getState());
+ componentDto.setValidationErrors(processorDto.getValidationErrors());
+ component.setComponent(componentDto);
+
+ return component;
+ }
+
+ public AffectedComponentEntity createAffectedComponentEntity(final PortEntity portEntity, final String referenceType) {
+ if (portEntity == null) {
+ return null;
+ }
+
+ final AffectedComponentEntity component = new AffectedComponentEntity();
+ component.setBulletins(portEntity.getBulletins());
+ component.setId(portEntity.getId());
+ component.setPermissions(portEntity.getPermissions());
+ component.setPosition(portEntity.getPosition());
+ component.setRevision(portEntity.getRevision());
+ component.setUri(portEntity.getUri());
+
+ final PortDTO portDto = portEntity.getComponent();
+ final AffectedComponentDTO componentDto = new AffectedComponentDTO();
+ componentDto.setId(portDto.getId());
+ componentDto.setName(portDto.getName());
+ componentDto.setProcessGroupId(portDto.getParentGroupId());
+ componentDto.setReferenceType(referenceType);
+ componentDto.setState(portDto.getState());
+ componentDto.setValidationErrors(portDto.getValidationErrors());
+ component.setComponent(componentDto);
+
+ return component;
+ }
+
+ public AffectedComponentEntity createAffectedComponentEntity(final ControllerServiceEntity serviceEntity) {
+ if (serviceEntity == null) {
+ return null;
+ }
+
+ final AffectedComponentEntity component = new AffectedComponentEntity();
+ component.setBulletins(serviceEntity.getBulletins());
+ component.setId(serviceEntity.getId());
+ component.setPermissions(serviceEntity.getPermissions());
+ component.setPosition(serviceEntity.getPosition());
+ component.setRevision(serviceEntity.getRevision());
+ component.setUri(serviceEntity.getUri());
+
+ final ControllerServiceDTO serviceDto = serviceEntity.getComponent();
+ final AffectedComponentDTO componentDto = new AffectedComponentDTO();
+ componentDto.setId(serviceDto.getId());
+ componentDto.setName(serviceDto.getName());
+ componentDto.setProcessGroupId(serviceDto.getParentGroupId());
+ componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_CONTROLLER_SERVICE);
+ componentDto.setState(serviceDto.getState());
+ componentDto.setValidationErrors(serviceDto.getValidationErrors());
+ component.setComponent(componentDto);
+
+ return component;
+ }
+
+ public AffectedComponentEntity createAffectedComponentEntity(final RemoteProcessGroupPortDTO remotePortDto, final String referenceType, final RemoteProcessGroupEntity rpgEntity) {
+ if (remotePortDto == null) {
+ return null;
+ }
+
+ final AffectedComponentEntity component = new AffectedComponentEntity();
+ component.setId(remotePortDto.getId());
+ component.setPermissions(rpgEntity.getPermissions());
+ component.setRevision(rpgEntity.getRevision());
+ component.setUri(rpgEntity.getUri());
+
+ final AffectedComponentDTO componentDto = new AffectedComponentDTO();
+ componentDto.setId(remotePortDto.getId());
+ componentDto.setName(remotePortDto.getName());
+ componentDto.setProcessGroupId(remotePortDto.getGroupId());
+ componentDto.setReferenceType(referenceType);
+ componentDto.setState(remotePortDto.isTransmitting() ? "Running" : "Stopped");
+ component.setComponent(componentDto);
+
+ return component;
+ }
+
+
+ public AffectedComponentDTO createAffectedComponentDto(final ComponentNode component) {
+ final AffectedComponentDTO dto = new AffectedComponentDTO();
+ dto.setId(component.getIdentifier());
+ dto.setName(component.getName());
+ dto.setProcessGroupId(component.getProcessGroupIdentifier());
+
+ if (component instanceof ProcessorNode) {
+ final ProcessorNode node = ((ProcessorNode) component);
+ dto.setState(node.getScheduledState().name());
+ dto.setActiveThreadCount(node.getActiveThreadCount());
+ dto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_PROCESSOR);
+ } else if (component instanceof ControllerServiceNode) {
+ final ControllerServiceNode node = ((ControllerServiceNode) component);
+ dto.setState(node.getState().name());
+ dto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_CONTROLLER_SERVICE);
+ }
+
+ final Collection<ValidationResult> validationErrors = component.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a ProcessGroupDTO from the specified ProcessGroup.
+ *
+ * @param group group
+ * @return dto
+ */
+ public ProcessGroupDTO createProcessGroupDto(final ProcessGroup group) {
+ return createProcessGroupDto(group, false);
+ }
+
+ public ProcessGroupFlowDTO createProcessGroupFlowDto(final ProcessGroup group, final ProcessGroupStatus groupStatus, final RevisionManager revisionManager,
+ final Function<ProcessGroup, List<BulletinEntity>> getProcessGroupBulletins) {
+
+ final ProcessGroupFlowDTO dto = new ProcessGroupFlowDTO();
+ dto.setId(group.getIdentifier());
+ dto.setLastRefreshed(new Date());
+ dto.setBreadcrumb(createBreadcrumbEntity(group));
+ dto.setFlow(createFlowDto(group, groupStatus, revisionManager, getProcessGroupBulletins));
+
+ final ProcessGroup parent = group.getParent();
+ if (parent != null) {
+ dto.setParentGroupId(parent.getIdentifier());
+ }
+
+ return dto;
+ }
+
+ public FlowDTO createFlowDto(final ProcessGroup group, final ProcessGroupStatus groupStatus, final FlowSnippetDTO snippet, final RevisionManager revisionManager,
+ final Function<ProcessGroup, List<BulletinEntity>> getProcessGroupBulletins) {
+ if (snippet == null) {
+ return null;
+ }
+
+ final FlowDTO flow = new FlowDTO();
+
+ for (final ConnectionDTO snippetConnection : snippet.getConnections()) {
+ final Connection connection = group.getConnection(snippetConnection.getId());
+
+ // marshal the actual connection as the snippet is pruned
+ final ConnectionDTO dto = createConnectionDto(connection);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(connection.getIdentifier()));
+ final PermissionsDTO accessPolicy = createPermissionsDto(connection);
+ final ConnectionStatusDTO status = getComponentStatus(
+ () -> groupStatus.getConnectionStatus().stream().filter(connectionStatus -> connection.getIdentifier().equals(connectionStatus.getId())).findFirst().orElse(null),
+ connectionStatus -> createConnectionStatusDto(connectionStatus)
+ );
+ flow.getConnections().add(entityFactory.createConnectionEntity(dto, revision, accessPolicy, status));
+ }
+
+ for (final FunnelDTO snippetFunnel : snippet.getFunnels()) {
+ final Funnel funnel = group.getFunnel(snippetFunnel.getId());
+
+ // marshal the actual funnel as the snippet is pruned
+ final FunnelDTO dto = createFunnelDto(funnel);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(funnel.getIdentifier()));
+ final PermissionsDTO accessPolicy = createPermissionsDto(funnel);
+ flow.getFunnels().add(entityFactory.createFunnelEntity(dto, revision, accessPolicy));
+ }
+
+ for (final PortDTO snippetInputPort : snippet.getInputPorts()) {
+ final Port inputPort = group.getInputPort(snippetInputPort.getId());
+
+ // marshal the actual port as the snippet is pruned
+ final PortDTO dto = createPortDto(inputPort);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(inputPort.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(inputPort);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(inputPort));
+ final PortStatusDTO status = getComponentStatus(
+ () -> groupStatus.getInputPortStatus().stream().filter(inputPortStatus -> inputPort.getIdentifier().equals(inputPortStatus.getId())).findFirst().orElse(null),
+ inputPortStatus -> createPortStatusDto(inputPortStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPort.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ flow.getInputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final PortDTO snippetOutputPort : snippet.getOutputPorts()) {
+ final Port outputPort = group.getOutputPort(snippetOutputPort.getId());
+
+ // marshal the actual port as the snippet is pruned
+ final PortDTO dto = createPortDto(outputPort);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(outputPort.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(outputPort);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(outputPort));
+ final PortStatusDTO status = getComponentStatus(
+ () -> groupStatus.getOutputPortStatus().stream().filter(outputPortStatus -> outputPort.getIdentifier().equals(outputPortStatus.getId())).findFirst().orElse(null),
+ outputPortStatus -> createPortStatusDto(outputPortStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPort.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ flow.getOutputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final LabelDTO snippetLabel : snippet.getLabels()) {
+ final Label label = group.getLabel(snippetLabel.getId());
+
+ // marshal the actual label as the snippet is pruned
+ final LabelDTO dto = createLabelDto(label);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(label.getIdentifier()));
+ final PermissionsDTO accessPolicy = createPermissionsDto(label);
+ flow.getLabels().add(entityFactory.createLabelEntity(dto, revision, accessPolicy));
+ }
+
+ for (final ProcessGroupDTO snippetProcessGroup : snippet.getProcessGroups()) {
+ final ProcessGroup processGroup = group.getProcessGroup(snippetProcessGroup.getId());
+
+ // marshal the actual group as the snippet is pruned
+ final ProcessGroupDTO dto = createProcessGroupDto(processGroup);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(processGroup.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(processGroup);
+ final ProcessGroupStatusDTO status = getComponentStatus(
+ () -> groupStatus.getProcessGroupStatus().stream().filter(processGroupStatus -> processGroup.getIdentifier().equals(processGroupStatus.getId())).findFirst().orElse(null),
+ processGroupStatus -> createConciseProcessGroupStatusDto(processGroupStatus)
+ );
+ final List<BulletinEntity> bulletins = getProcessGroupBulletins.apply(processGroup);
+ flow.getProcessGroups().add(entityFactory.createProcessGroupEntity(dto, revision, permissions, status, bulletins));
+ }
+
+ for (final ProcessorDTO snippetProcessor : snippet.getProcessors()) {
+ final ProcessorNode processor = group.getProcessor(snippetProcessor.getId());
+
+ // marshal the actual processor as the snippet is pruned
+ final ProcessorDTO dto = createProcessorDto(processor);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(processor.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(processor);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(processor));
+ final ProcessorStatusDTO status = getComponentStatus(
+ () -> groupStatus.getProcessorStatus().stream().filter(processorStatus -> processor.getIdentifier().equals(processorStatus.getId())).findFirst().orElse(null),
+ processorStatus -> createProcessorStatusDto(processorStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(processor.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ flow.getProcessors().add(entityFactory.createProcessorEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final RemoteProcessGroupDTO snippetRemoteProcessGroup : snippet.getRemoteProcessGroups()) {
+ final RemoteProcessGroup remoteProcessGroup = group.getRemoteProcessGroup(snippetRemoteProcessGroup.getId());
+
+ // marshal the actual rpm as the snippet is pruned
+ final RemoteProcessGroupDTO dto = createRemoteProcessGroupDto(remoteProcessGroup);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(remoteProcessGroup.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(remoteProcessGroup);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
+ final RemoteProcessGroupStatusDTO status = getComponentStatus(
+ () -> groupStatus.getRemoteProcessGroupStatus().stream().filter(rpgStatus -> remoteProcessGroup.getIdentifier().equals(rpgStatus.getId())).findFirst().orElse(null),
+ remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(remoteProcessGroup, remoteProcessGroupStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroup.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ flow.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ return flow;
+ }
+
+ private <T, S> T getComponentStatus(final Supplier<S> getComponentStatus, final Function<S, T> convertToDto) {
+ final T statusDTO;
+ final S status = getComponentStatus.get();
+ if (status != null) {
+ statusDTO = convertToDto.apply(status);
+ } else {
+ statusDTO = null;
+ }
+ return statusDTO;
+ }
+
+ public FlowDTO createFlowDto(final ProcessGroup group, final ProcessGroupStatus groupStatus, final RevisionManager revisionManager,
+ final Function<ProcessGroup, List<BulletinEntity>> getProcessGroupBulletins) {
+ final FlowDTO dto = new FlowDTO();
+
+ for (final ProcessorNode procNode : group.getProcessors()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(procNode.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(procNode);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(procNode));
+ final ProcessorStatusDTO status = getComponentStatus(
+ () -> groupStatus.getProcessorStatus().stream().filter(processorStatus -> procNode.getIdentifier().equals(processorStatus.getId())).findFirst().orElse(null),
+ processorStatus -> createProcessorStatusDto(processorStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(procNode.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ dto.getProcessors().add(entityFactory.createProcessorEntity(createProcessorDto(procNode), revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final Connection connNode : group.getConnections()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(connNode.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(connNode);
+ final ConnectionStatusDTO status = getComponentStatus(
+ () -> groupStatus.getConnectionStatus().stream().filter(connectionStatus -> connNode.getIdentifier().equals(connectionStatus.getId())).findFirst().orElse(null),
+ connectionStatus -> createConnectionStatusDto(connectionStatus)
+ );
+ dto.getConnections().add(entityFactory.createConnectionEntity(createConnectionDto(connNode), revision, permissions, status));
+ }
+
+ for (final Label label : group.getLabels()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(label.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(label);
+ dto.getLabels().add(entityFactory.createLabelEntity(createLabelDto(label), revision, permissions));
+ }
+
+ for (final Funnel funnel : group.getFunnels()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(funnel.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(funnel);
+ dto.getFunnels().add(entityFactory.createFunnelEntity(createFunnelDto(funnel), revision, permissions));
+ }
+
+ for (final ProcessGroup childGroup : group.getProcessGroups()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(childGroup.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(childGroup);
+ final ProcessGroupStatusDTO status = getComponentStatus(
+ () -> groupStatus.getProcessGroupStatus().stream().filter(processGroupStatus -> childGroup.getIdentifier().equals(processGroupStatus.getId())).findFirst().orElse(null),
+ processGroupStatus -> createConciseProcessGroupStatusDto(processGroupStatus)
+ );
+ final List<BulletinEntity> bulletins = getProcessGroupBulletins.apply(childGroup);
+ dto.getProcessGroups().add(entityFactory.createProcessGroupEntity(createProcessGroupDto(childGroup), revision, permissions, status, bulletins));
+ }
+
+ for (final RemoteProcessGroup rpg : group.getRemoteProcessGroups()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(rpg.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(rpg);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(rpg));
+ final RemoteProcessGroupStatusDTO status = getComponentStatus(
+ () -> groupStatus.getRemoteProcessGroupStatus().stream().filter(remoteProcessGroupStatus -> rpg.getIdentifier().equals(remoteProcessGroupStatus.getId())).findFirst().orElse(null),
+ remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(rpg, remoteProcessGroupStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(rpg.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ dto.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(createRemoteProcessGroupDto(rpg), revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final Port inputPort : group.getInputPorts()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(inputPort.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(inputPort);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(inputPort));
+ final PortStatusDTO status = getComponentStatus(
+ () -> groupStatus.getInputPortStatus().stream().filter(inputPortStatus -> inputPort.getIdentifier().equals(inputPortStatus.getId())).findFirst().orElse(null),
+ inputPortStatus -> createPortStatusDto(inputPortStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPort.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ dto.getInputPorts().add(entityFactory.createPortEntity(createPortDto(inputPort), revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ for (final Port outputPort : group.getOutputPorts()) {
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(outputPort.getIdentifier()));
+ final PermissionsDTO permissions = createPermissionsDto(outputPort);
+ final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(outputPort));
+ final PortStatusDTO status = getComponentStatus(
+ () -> groupStatus.getOutputPortStatus().stream().filter(outputPortStatus -> outputPort.getIdentifier().equals(outputPortStatus.getId())).findFirst().orElse(null),
+ outputPortStatus -> createPortStatusDto(outputPortStatus)
+ );
+ final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPort.getIdentifier()));
+ final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
+ dto.getOutputPorts().add(entityFactory.createPortEntity(createPortDto(outputPort), revision, permissions, operatePermissions, status, bulletinEntities));
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a ProcessGroupDTO from the specified ProcessGroup.
+ *
+ * @param group group
+ * @param recurse recurse
+ * @return dto
+ */
+ public ProcessGroupDTO createProcessGroupDto(final ProcessGroup group, final boolean recurse) {
+ final ProcessGroupDTO dto = createConciseProcessGroupDto(group);
+ dto.setContents(createProcessGroupContentsDto(group, recurse));
+ return dto;
+ }
+
+ /**
+ * Creates a ProcessGroupDTO from the specified ProcessGroup.
+ *
+ * @param group group
+ * @return dto
+ */
+ private ProcessGroupDTO createConciseProcessGroupDto(final ProcessGroup group) {
+ if (group == null) {
+ return null;
+ }
+
+ final ProcessGroupDTO dto = new ProcessGroupDTO();
+ dto.setId(group.getIdentifier());
+ dto.setPosition(createPositionDto(group.getPosition()));
+ dto.setComments(group.getComments());
+ dto.setName(group.getName());
+ dto.setVersionedComponentId(group.getVersionedComponentId().orElse(null));
+ dto.setVersionControlInformation(createVersionControlInformationDto(group));
+
+ final Map<String, String> variables = group.getVariableRegistry().getVariableMap().entrySet().stream()
+ .collect(Collectors.toMap(entry -> entry.getKey().getName(), entry -> entry.getValue()));
+ dto.setVariables(variables);
+
+ final ProcessGroup parentGroup = group.getParent();
+ if (parentGroup != null) {
+ dto.setParentGroupId(parentGroup.getIdentifier());
+ }
+
+ final ProcessGroupCounts counts = group.getCounts();
+ dto.setRunningCount(counts.getRunningCount());
+ dto.setStoppedCount(counts.getStoppedCount());
+ dto.setInvalidCount(counts.getInvalidCount());
+ dto.setDisabledCount(counts.getDisabledCount());
+ dto.setInputPortCount(counts.getInputPortCount());
+ dto.setOutputPortCount(counts.getOutputPortCount());
+ dto.setActiveRemotePortCount(counts.getActiveRemotePortCount());
+ dto.setInactiveRemotePortCount(counts.getInactiveRemotePortCount());
+ dto.setUpToDateCount(counts.getUpToDateCount());
+ dto.setLocallyModifiedCount(counts.getLocallyModifiedCount());
+ dto.setStaleCount(counts.getStaleCount());
+ dto.setLocallyModifiedAndStaleCount(counts.getLocallyModifiedAndStaleCount());
+ dto.setSyncFailureCount(counts.getSyncFailureCount());
+
+ return dto;
+ }
+
+
+ public Set<ComponentDifferenceDTO> createComponentDifferenceDtos(final FlowComparison comparison) {
+ final Map<ComponentDifferenceDTO, List<DifferenceDTO>> differencesByComponent = new HashMap<>();
+
+ for (final FlowDifference difference : comparison.getDifferences()) {
+ // Ignore these as local differences for now because we can't do anything with it
+ if (difference.getDifferenceType() == DifferenceType.BUNDLE_CHANGED) {
+ continue;
+ }
+
+ // Ignore differences for adding remote ports
+ if (FlowDifferenceFilters.isAddedOrRemovedRemotePort(difference)) {
+ continue;
+ }
+
+ if (FlowDifferenceFilters.isIgnorableVersionedFlowCoordinateChange(difference)) {
+ continue;
+ }
+
+ final ComponentDifferenceDTO componentDiff = createComponentDifference(difference);
+ final List<DifferenceDTO> differences = differencesByComponent.computeIfAbsent(componentDiff, key -> new ArrayList<>());
+
+ final DifferenceDTO dto = new DifferenceDTO();
+ dto.setDifferenceType(difference.getDifferenceType().getDescription());
+ dto.setDifference(difference.getDescription());
+
+ differences.add(dto);
+ }
+
+ for (final Map.Entry<ComponentDifferenceDTO, List<DifferenceDTO>> entry : differencesByComponent.entrySet()) {
+ entry.getKey().setDifferences(entry.getValue());
+ }
+
+ return differencesByComponent.keySet();
+ }
+
+ private ComponentDifferenceDTO createComponentDifference(final FlowDifference difference) {
+ VersionedComponent component = difference.getComponentA();
+ if (component == null || difference.getComponentB() instanceof InstantiatedVersionedComponent) {
+ component = difference.getComponentB();
+ }
+
+ final ComponentDifferenceDTO dto = new ComponentDifferenceDTO();
+ dto.setComponentName(component.getName());
+ dto.setComponentType(component.getComponentType().toString());
+
+ if (component instanceof InstantiatedVersionedComponent) {
+ final InstantiatedVersionedComponent instantiatedComponent = (InstantiatedVersionedComponent) component;
+ dto.setComponentId(instantiatedComponent.getInstanceId());
+ dto.setProcessGroupId(instantiatedComponent.getInstanceGroupId());
+ } else {
+ dto.setComponentId(component.getIdentifier());
+ dto.setProcessGroupId(dto.getProcessGroupId());
+ }
+
+ return dto;
+ }
+
+
+ public VersionControlInformationDTO createVersionControlInformationDto(final ProcessGroup group) {
+ if (group == null) {
+ return null;
+ }
+
+ final VersionControlInformation versionControlInfo = group.getVersionControlInformation();
+ if (versionControlInfo == null) {
+ return null;
+ }
+
+ final VersionControlInformationDTO dto = new VersionControlInformationDTO();
+ dto.setGroupId(group.getIdentifier());
+ dto.setRegistryId(versionControlInfo.getRegistryIdentifier());
+ dto.setRegistryName(versionControlInfo.getRegistryName());
+ dto.setBucketId(versionControlInfo.getBucketIdentifier());
+ dto.setBucketName(versionControlInfo.getBucketName());
+ dto.setFlowId(versionControlInfo.getFlowIdentifier());
+ dto.setFlowName(versionControlInfo.getFlowName());
+ dto.setFlowDescription(versionControlInfo.getFlowDescription());
+ dto.setVersion(versionControlInfo.getVersion());
+
+ final VersionedFlowStatus status = versionControlInfo.getStatus();
+ final VersionedFlowState state = status.getState();
+ dto.setState(state == null ? null : state.name());
+ dto.setStateExplanation(status.getStateExplanation());
+
+ return dto;
+ }
+
+ public Map<String, String> createVersionControlComponentMappingDto(final InstantiatedVersionedProcessGroup group) {
+ final Map<String, String> mapping = new HashMap<>();
+
+ mapping.put(group.getInstanceId(), group.getIdentifier());
+ group.getProcessors().stream()
+ .map(proc -> (InstantiatedVersionedProcessor) proc)
+ .forEach(proc -> mapping.put(proc.getInstanceId(), proc.getIdentifier()));
+ group.getFunnels().stream()
+ .map(funnel -> (InstantiatedVersionedFunnel) funnel)
+ .forEach(funnel -> mapping.put(funnel.getInstanceId(), funnel.getIdentifier()));
+ group.getInputPorts().stream()
+ .map(port -> (InstantiatedVersionedPort) port)
+ .forEach(port -> mapping.put(port.getInstanceId(), port.getIdentifier()));
+ group.getOutputPorts().stream()
+ .map(port -> (InstantiatedVersionedPort) port)
+ .forEach(port -> mapping.put(port.getInstanceId(), port.getIdentifier()));
+ group.getControllerServices().stream()
+ .map(service -> (InstantiatedVersionedControllerService) service)
+ .forEach(service -> mapping.put(service.getInstanceId(), service.getIdentifier()));
+ group.getLabels().stream()
+ .map(label -> (InstantiatedVersionedLabel) label)
+ .forEach(label -> mapping.put(label.getInstanceId(), label.getIdentifier()));
+ group.getConnections().stream()
+ .map(conn -> (InstantiatedVersionedConnection) conn)
+ .forEach(conn -> mapping.put(conn.getInstanceId(), conn.getIdentifier()));
+ group.getRemoteProcessGroups().stream()
+ .map(rpg -> (InstantiatedVersionedRemoteProcessGroup) rpg)
+ .forEach(rpg -> {
+ mapping.put(rpg.getInstanceId(), rpg.getIdentifier());
+
+ if (rpg.getInputPorts() != null) {
+ rpg.getInputPorts().stream()
+ .map(port -> (InstantiatedVersionedRemoteGroupPort) port)
+ .forEach(port -> mapping.put(port.getInstanceId(), port.getIdentifier()));
+ }
+
+ if (rpg.getOutputPorts() != null) {
+ rpg.getOutputPorts().stream()
+ .map(port -> (InstantiatedVersionedRemoteGroupPort) port)
+ .forEach(port -> mapping.put(port.getInstanceId(), port.getIdentifier()));
+ }
+ });
+
+ group.getProcessGroups().stream()
+ .map(child -> (InstantiatedVersionedProcessGroup) child)
+ .forEach(child -> {
+ final Map<String, String> childMapping = createVersionControlComponentMappingDto(child);
+ mapping.putAll(childMapping);
+ });
+
+ return mapping;
+ }
+
+
+ /**
+ * Creates a ProcessGroupContentDTO from the specified ProcessGroup.
+ *
+ * @param group group
+ * @param recurse recurse
+ * @return dto
+ */
+ private FlowSnippetDTO createProcessGroupContentsDto(final ProcessGroup group, final boolean recurse) {
+ if (group == null) {
+ return null;
+ }
+
+ final FlowSnippetDTO dto = new FlowSnippetDTO();
+
+ for (final ProcessorNode procNode : group.getProcessors()) {
+ dto.getProcessors().add(createProcessorDto(procNode));
+ }
+
+ for (final Connection connNode : group.getConnections()) {
+ dto.getConnections().add(createConnectionDto(connNode));
+ }
+
+ for (final Label label : group.getLabels()) {
+ dto.getLabels().add(createLabelDto(label));
+ }
+
+ for (final Funnel funnel : group.getFunnels()) {
+ dto.getFunnels().add(createFunnelDto(funnel));
+ }
+
+ for (final ProcessGroup childGroup : group.getProcessGroups()) {
+ if (recurse) {
+ dto.getProcessGroups().add(createProcessGroupDto(childGroup, recurse));
+ } else {
+ dto.getProcessGroups().add(createConciseProcessGroupDto(childGroup));
+ }
+ }
+
+ for (final RemoteProcessGroup remoteProcessGroup : group.getRemoteProcessGroups()) {
+ dto.getRemoteProcessGroups().add(createRemoteProcessGroupDto(remoteProcessGroup));
+ }
+
+ for (final Port inputPort : group.getInputPorts()) {
+ dto.getInputPorts().add(createPortDto(inputPort));
+ }
+
+ for (final Port outputPort : group.getOutputPorts()) {
+ dto.getOutputPorts().add(createPortDto(outputPort));
+ }
+
+ return dto;
+ }
+
+ private boolean isRestricted(final Class<?> cls) {
+ return cls.isAnnotationPresent(Restricted.class);
+ }
+
+ private String getUsageRestriction(final Class<?> cls) {
+ final Restricted restricted = cls.getAnnotation(Restricted.class);
+
+ if (restricted == null) {
+ return null;
+ }
+
+ if (StringUtils.isBlank(restricted.value())) {
+ return null;
+ }
+
+ return restricted.value();
+ }
+
+ private Set<ExplicitRestrictionDTO> getExplicitRestrictions(final Class<?> cls) {
+ final Restricted restricted = cls.getAnnotation(Restricted.class);
+
+ if (restricted == null) {
+ return null;
+ }
+
+ final Restriction[] restrictions = restricted.restrictions();
+
+ if (restrictions == null || restrictions.length == 0) {
+ return null;
+ }
+
+ return Arrays.stream(restrictions).map(restriction -> {
+ final RequiredPermissionDTO requiredPermission = new RequiredPermissionDTO();
+ requiredPermission.setId(restriction.requiredPermission().getPermissionIdentifier());
+ requiredPermission.setLabel(restriction.requiredPermission().getPermissionLabel());
+
+ final ExplicitRestrictionDTO usageRestriction = new ExplicitRestrictionDTO();
+ usageRestriction.setRequiredPermission(requiredPermission);
+ usageRestriction.setExplanation(restriction.explanation());
+ return usageRestriction;
+ }).collect(Collectors.toSet());
+ }
+
+ private String getDeprecationReason(final Class<?> cls) {
+ final DeprecationNotice deprecationNotice = cls.getAnnotation(DeprecationNotice.class);
+ return deprecationNotice == null ? null : deprecationNotice.reason();
+ }
+
+ public Set<AffectedComponentEntity> createAffectedComponentEntities(final Set<ComponentNode> affectedComponents, final RevisionManager revisionManager) {
+ return affectedComponents.stream()
+ .map(component -> {
+ final AffectedComponentDTO affectedComponent = createAffectedComponentDto(component);
+ final PermissionsDTO permissions = createPermissionsDto(component);
+ final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(component.getIdentifier()));
+ return entityFactory.createAffectedComponentEntity(affectedComponent, revision, permissions);
+ })
+ .collect(Collectors.toSet());
+ }
+
+ public VariableRegistryDTO createVariableRegistryDto(final ProcessGroup processGroup, final RevisionManager revisionManager) {
+ final ComponentVariableRegistry variableRegistry = processGroup.getVariableRegistry();
+
+ final List<String> variableNames = variableRegistry.getVariableMap().keySet().stream()
+ .map(descriptor -> descriptor.getName())
+ .collect(Collectors.toList());
+
+ final Set<VariableEntity> variableEntities = new LinkedHashSet<>();
+
+ for (final String variableName : variableNames) {
+ final VariableDTO variableDto = new VariableDTO();
+ variableDto.setName(variableName);
+ variableDto.setValue(variableRegistry.getVariableValue(variableName));
+ variableDto.setProcessGroupId(processGroup.getIdentifier());
+
+ final Set<AffectedComponentEntity> affectedComponentEntities = createAffectedComponentEntities(processGroup.getComponentsAffectedByVariable(variableName), revisionManager);
+
+ boolean canWrite = true;
+ for (final AffectedComponentEntity affectedComponent : affectedComponentEntities) {
+ final PermissionsDTO permissions = affectedComponent.getPermissions();
+ if (!permissions.getCanRead() || !permissions.getCanWrite()) {
+ canWrite = false;
+ break;
+ }
+ }
+
+ variableDto.setAffectedComponents(affectedComponentEntities);
+
+ final VariableEntity variableEntity = new VariableEntity();
+ variableEntity.setVariable(variableDto);
+ variableEntity.setCanWrite(canWrite);
+
+ variableEntities.add(variableEntity);
+ }
+
+ final VariableRegistryDTO registryDto = new VariableRegistryDTO();
+ registryDto.setProcessGroupId(processGroup.getIdentifier());
+ registryDto.setVariables(variableEntities);
+
+ return registryDto;
+ }
+
+ public VariableRegistryUpdateRequestDTO createVariableRegistryUpdateRequestDto(final VariableRegistryUpdateRequest request) {
+ final VariableRegistryUpdateRequestDTO dto = new VariableRegistryUpdateRequestDTO();
+ dto.setComplete(request.isComplete());
+ dto.setFailureReason(request.getFailureReason());
+ dto.setLastUpdated(request.getLastUpdated());
+ dto.setProcessGroupId(request.getProcessGroupId());
+ dto.setRequestId(request.getRequestId());
+ dto.setSubmissionTime(request.getSubmissionTime());
+
+ final List<VariableRegistryUpdateStepDTO> updateSteps = new ArrayList<>();
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getIdentifyRelevantComponentsStep()));
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getStopProcessorsStep()));
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getDisableServicesStep()));
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getApplyUpdatesStep()));
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getEnableServicesStep()));
+ updateSteps.add(createVariableRegistryUpdateStepDto(request.getStartProcessorsStep()));
+ dto.setUpdateSteps(updateSteps);
+
+ dto.setAffectedComponents(new HashSet<>(request.getAffectedComponents().values()));
+
+ return dto;
+ }
+
+ public VariableRegistryUpdateStepDTO createVariableRegistryUpdateStepDto(final VariableRegistryUpdateStep step) {
+ final VariableRegistryUpdateStepDTO dto = new VariableRegistryUpdateStepDTO();
+ dto.setComplete(step.isComplete());
+ dto.setDescription(step.getDescription());
+ dto.setFailureReason(step.getFailureReason());
+ return dto;
+ }
+
+
+ public VariableRegistryDTO populateAffectedComponents(final VariableRegistryDTO variableRegistry, final ProcessGroup group, final RevisionManager revisionManager) {
+ if (!group.getIdentifier().equals(variableRegistry.getProcessGroupId())) {
+ throw new IllegalArgumentException("Variable Registry does not have the same Group ID as the given Process Group");
+ }
+
+ final Set<VariableEntity> variableEntities = new LinkedHashSet<>();
+
+ if (variableRegistry.getVariables() != null) {
+ for (final VariableEntity inputEntity : variableRegistry.getVariables()) {
+ final VariableEntity entity = new VariableEntity();
+
+ final VariableDTO inputDto = inputEntity.getVariable();
+ final VariableDTO variableDto = new VariableDTO();
+ variableDto.setName(inputDto.getName());
+ variableDto.setValue(inputDto.getValue());
+ variableDto.setProcessGroupId(group.getIdentifier());
+
+ final Set<AffectedComponentEntity> affectedComponentEntities = createAffectedComponentEntities(group.getComponentsAffectedByVariable(variableDto.getName()), revisionManager);
+
+ boolean canWrite = true;
+ for (final AffectedComponentEntity affectedComponent : affectedComponentEntities) {
+ final PermissionsDTO permissions = affectedComponent.getPermissions();
+ if (!permissions.getCanRead() || !permissions.getCanWrite()) {
+ canWrite = false;
+ break;
+ }
+ }
+
+ variableDto.setAffectedComponents(affectedComponentEntities);
+
+ entity.setCanWrite(canWrite);
+ entity.setVariable(inputDto);
+
+ variableEntities.add(entity);
+ }
+ }
+
+ final VariableRegistryDTO registryDto = new VariableRegistryDTO();
+ registryDto.setProcessGroupId(group.getIdentifier());
+ registryDto.setVariables(variableEntities);
+
+ return registryDto;
+ }
+
+
+ /**
+ * Gets the capability description from the specified class.
+ */
+ private String getCapabilityDescription(final Class<?> cls) {
+ final CapabilityDescription capabilityDesc = cls.getAnnotation(CapabilityDescription.class);
+ return capabilityDesc == null ? null : capabilityDesc.value();
+ }
+
+ /**
+ * Gets the tags from the specified class.
+ */
+ private Set<String> getTags(final Class<?> cls) {
+ final Set<String> tags = new HashSet<>();
+ final Tags tagsAnnotation = cls.getAnnotation(Tags.class);
+ if (tagsAnnotation != null) {
+ for (final String tag : tagsAnnotation.value()) {
+ tags.add(tag);
+ }
+ }
+
+ if (cls.isAnnotationPresent(Restricted.class)) {
+ tags.add("restricted");
+ }
+
+ return tags;
+ }
+
+ /**
+ * Creates a bundle DTO from the specified class.
+ *
+ * @param coordinate bundle coordinates
+ * @return dto
+ */
+ public BundleDTO createBundleDto(final BundleCoordinate coordinate) {
+ final BundleDTO dto = new BundleDTO();
+ dto.setGroup(coordinate.getGroup());
+ dto.setArtifact(coordinate.getId());
+ dto.setVersion(coordinate.getVersion());
+ return dto;
+ }
+
+ private List<ControllerServiceApiDTO> createControllerServiceApiDto(final Class cls) {
+ final Set<Class> serviceApis = new HashSet<>();
+
+ // if this is a controller service
+ if (ControllerService.class.isAssignableFrom(cls)) {
+ // get all of it's interfaces to determine the controller service api's it implements
+ final List<Class<?>> interfaces = ClassUtils.getAllInterfaces(cls);
+ for (final Class i : interfaces) {
+ // add all controller services that's not ControllerService itself
+ if (ControllerService.class.isAssignableFrom(i) && !ControllerService.class.equals(i)) {
+ serviceApis.add(i);
+ }
+ }
+
+ final List<ControllerServiceApiDTO> dtos = new ArrayList<>();
+ for (final Class serviceApi : serviceApis) {
+ final Bundle bundle = extensionManager.getBundle(serviceApi.getClassLoader());
+ final BundleCoordinate bundleCoordinate = bundle.getBundleDetails().getCoordinate();
+
+ final ControllerServiceApiDTO dto = new ControllerServiceApiDTO();
+ dto.setType(serviceApi.getName());
+ dto.setBundle(createBundleDto(bundleCoordinate));
+ dtos.add(dto);
+ }
+ return dtos;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the DocumentedTypeDTOs from the specified classes.
+ *
+ * @param classes classes
+ * @param bundleGroupFilter if specified, must be member of bundle group
+ * @param bundleArtifactFilter if specified, must be member of bundle artifact
+ * @param typeFilter if specified, type must match
+ * @return dtos
+ */
+ public Set<DocumentedTypeDTO> fromDocumentedTypes(final Map<Class, Bundle> classes, final String bundleGroupFilter, final String bundleArtifactFilter, final String typeFilter) {
+ final Set<DocumentedTypeDTO> types = new LinkedHashSet<>();
+ final List<Class> sortedClasses = new ArrayList<>(classes.keySet());
+ Collections.sort(sortedClasses, CLASS_NAME_COMPARATOR);
+
+ for (final Class cls : sortedClasses) {
+ final Bundle bundle = classes.get(cls);
+ final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate();
+
+ // only include classes that meet the criteria if specified
+ if (bundleGroupFilter != null && !bundleGroupFilter.equals(coordinate.getGroup())) {
+ continue;
+ }
+ if (bundleArtifactFilter != null && !bundleArtifactFilter.equals(coordinate.getId())) {
+ continue;
+ }
+ if (typeFilter != null && !typeFilter.equals(cls.getName())) {
+ continue;
+ }
+
+ final DocumentedTypeDTO dto = new DocumentedTypeDTO();
+ dto.setType(cls.getName());
+ dto.setBundle(createBundleDto(coordinate));
+ dto.setControllerServiceApis(createControllerServiceApiDto(cls));
+ dto.setDescription(getCapabilityDescription(cls));
+ dto.setRestricted(isRestricted(cls));
+ dto.setUsageRestriction(getUsageRestriction(cls));
+ dto.setExplicitRestrictions(getExplicitRestrictions(cls));
+ dto.setDeprecationReason(getDeprecationReason(cls));
+ dto.setTags(getTags(cls));
+ types.add(dto);
+ }
+
+ return types;
+ }
+
+ /**
+ * Gets the DocumentedTypeDTOs from the specified classes.
+ *
+ * @param classes classes
+ * @param bundleGroupFilter if specified, must be member of bundle group
+ * @param bundleArtifactFilter if specified, must be member of bundle artifact
+ * @param typeFilter if specified, type must match
+ * @return dtos
+ */
+ public Set<DocumentedTypeDTO> fromDocumentedTypes(final Set<Class> classes, final String bundleGroupFilter, final String bundleArtifactFilter, final String typeFilter) {
+ final Map<Class, Bundle> classBundles = new HashMap<>();
+ for (final Class cls : classes) {
+ classBundles.put(cls, extensionManager.getBundle(cls.getClassLoader()));
+ }
+ return fromDocumentedTypes(classBundles, bundleGroupFilter, bundleArtifactFilter, typeFilter);
+ }
+
+ /**
+ * Creates a ProcessorDTO from the specified ProcessorNode.
+ *
+ * @param node node
+ * @return dto
+ */
+ public ProcessorDTO createProcessorDto(final ProcessorNode node) {
+ if (node == null) {
+ return null;
+ }
+
+ final BundleCoordinate bundleCoordinate = node.getBundleCoordinate();
+ final List<Bundle> compatibleBundles = extensionManager.getBundles(node.getCanonicalClassName()).stream().filter(bundle -> {
+ final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate();
+ return bundleCoordinate.getGroup().equals(coordinate.getGroup()) && bundleCoordinate.getId().equals(coordinate.getId());
+ }).collect(Collectors.toList());
+
+ final ProcessorDTO dto = new ProcessorDTO();
+ dto.setId(node.getIdentifier());
+ dto.setPosition(createPositionDto(node.getPosition()));
+ dto.setStyle(node.getStyle());
+ dto.setParentGroupId(node.getProcessGroup().getIdentifier());
+ dto.setInputRequirement(node.getInputRequirement().name());
+ dto.setPersistsState(node.getProcessor().getClass().isAnnotationPresent(Stateful.class));
+ dto.setRestricted(node.isRestricted());
+ dto.setDeprecated(node.isDeprecated());
+ dto.setExecutionNodeRestricted(node.isExecutionNodeRestricted());
+ dto.setExtensionMissing(node.isExtensionMissing());
+ dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1);
+ dto.setVersionedComponentId(node.getVersionedComponentId().orElse(null));
+
+ dto.setType(node.getCanonicalClassName());
+ dto.setBundle(createBundleDto(bundleCoordinate));
+ dto.setName(node.getName());
+ dto.setState(node.getScheduledState().toString());
+
+ // build the relationship dtos
+ final List<RelationshipDTO> relationships = new ArrayList<>();
+ for (final Relationship rel : node.getRelationships()) {
+ final RelationshipDTO relationshipDTO = new RelationshipDTO();
+ relationshipDTO.setDescription(rel.getDescription());
+ relationshipDTO.setName(rel.getName());
+ relationshipDTO.setAutoTerminate(node.isAutoTerminated(rel));
+ relationships.add(relationshipDTO);
+ }
+
+ // sort the relationships
+ Collections.sort(relationships, new Comparator<RelationshipDTO>() {
+ @Override
+ public int compare(final RelationshipDTO r1, final RelationshipDTO r2) {
+ return Collator.getInstance(Locale.US).compare(r1.getName(), r2.getName());
+ }
+ });
+
+ // set the relationships
+ dto.setRelationships(relationships);
+
+ dto.setDescription(getCapabilityDescription(node.getClass()));
+ dto.setSupportsParallelProcessing(!node.isTriggeredSerially());
+ dto.setSupportsEventDriven(node.isEventDrivenSupported());
+ dto.setSupportsBatching(node.isSessionBatchingSupported());
+ dto.setConfig(createProcessorConfigDto(node));
+
+ final ValidationStatus validationStatus = node.getValidationStatus(1, TimeUnit.MILLISECONDS);
+ dto.setValidationStatus(validationStatus.name());
+
+ final Collection<ValidationResult> validationErrors = node.getValidationErrors();
+ if (validationErrors != null && !validationErrors.isEmpty()) {
+ final List<String> errors = new ArrayList<>();
+ for (final ValidationResult validationResult : validationErrors) {
+ errors.add(validationResult.toString());
+ }
+
+ dto.setValidationErrors(errors);
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a BulletinBoardDTO for the specified bulletins.
+ *
+ * @param bulletins bulletins
+ * @return dto
+ */
+ public BulletinBoardDTO createBulletinBoardDto(final List<BulletinEntity> bulletins) {
+ // sort the bulletins
+ Collections.sort(bulletins, new Comparator<BulletinEntity>() {
+ @Override
+ public int compare(final BulletinEntity bulletin1, final BulletinEntity bulletin2) {
+ if (bulletin1 == null && bulletin2 == null) {
+ return 0;
+ } else if (bulletin1 == null) {
+ return 1;
+ } else if (bulletin2 == null) {
+ return -1;
+ }
+
+ final Date timestamp1 = bulletin1.getTimestamp();
+ final Date timestamp2 = bulletin2.getTimestamp();
+ if (timestamp1 == null && timestamp2 == null) {
+ return 0;
+ } else if (timestamp1 == null) {
+ return 1;
+ } else if (timestamp2 == null) {
+ return -1;
+ } else {
+ return timestamp1.compareTo(timestamp2);
+ }
+ }
+ });
+
+ // create the bulletin board
+ final BulletinBoardDTO bulletinBoard = new BulletinBoardDTO();
+ bulletinBoard.setBulletins(bulletins);
+ bulletinBoard.setGenerated(new Date());
+ return bulletinBoard;
+ }
+
+ /**
+ * Creates BulletinDTOs for the specified Bulletins.
+ *
+ * @param bulletins bulletin
+ * @return dto
+ */
+ public List<BulletinDTO> createBulletinDtos(final List<Bulletin> bulletins) {
+ final List<BulletinDTO> bulletinDtos = new ArrayList<>(bulletins.size());
+ for (final Bulletin bulletin : bulletins) {
+ bulletinDtos.add(createBulletinDto(bulletin));
+ }
+ return bulletinDtos;
+ }
+
+ /**
+ * Creates a BulletinDTO for the specified Bulletin.
+ *
+ * @param bulletin bulletin
+ * @return dto
+ */
+ public BulletinDTO createBulletinDto(final Bulletin bulletin) {
+ final BulletinDTO dto = new BulletinDTO();
+ dto.setId(bulletin.getId());
+ dto.setNodeAddress(bulletin.getNodeAddress());
+ dto.setTimestamp(bulletin.getTimestamp());
+ dto.setGroupId(bulletin.getGroupId());
+ dto.setSourceId(bulletin.getSourceId());
+ dto.setSourceName(bulletin.getSourceName());
+ dto.setCategory(bulletin.getCategory());
+ dto.setLevel(bulletin.getLevel());
+ dto.setMessage(bulletin.getMessage());
+ return dto;
+ }
+
+ /**
+ * Creates a ProvenanceEventNodeDTO for the specified ProvenanceEventLineageNode.
+ *
+ * @param node node
+ * @return dto
+ */
+ public ProvenanceNodeDTO createProvenanceEventNodeDTO(final ProvenanceEventLineageNode node) {
+ final ProvenanceNodeDTO dto = new ProvenanceNodeDTO();
+ dto.setId(node.getIdentifier());
+ dto.setType("EVENT");
+ dto.setEventType(node.getEventType().toString());
+ dto.setTimestamp(new Date(node.getTimestamp()));
+ dto.setMillis(node.getTimestamp());
+ dto.setFlowFileUuid(node.getFlowFileUuid());
+ dto.setParentUuids(node.getParentUuids());
+ dto.setChildUuids(node.getChildUuids());
+ return dto;
+ }
+
+ /**
+ * Creates a FlowFileNodeDTO for the specified LineageNode.
+ *
+ * @param node node
+ * @return dto
+ */
+ public ProvenanceNodeDTO createFlowFileNodeDTO(final LineageNode node) {
+ final ProvenanceNodeDTO dto = new ProvenanceNodeDTO();
+ dto.setId(node.getIdentifier());
+ dto.setType("FLOWFILE");
+ dto.setTimestamp(new Date(node.getTimestamp()));
+ dto.setMillis(node.getTimestamp());
+ dto.setFlowFileUuid(node.getFlowFileUuid());
+ return dto;
+ }
+
+ /**
+ * Creates a ProvenanceLinkDTO for the specified LineageEdge.
+ *
+ * @param edge edge
+ * @return dto
+ */
+ public ProvenanceLinkDTO createProvenanceLinkDTO(final LineageEdge edge) {
+ final LineageNode source = edge.getSource();
+ final LineageNode target = edge.getDestination();
+
+ final ProvenanceLinkDTO dto = new ProvenanceLinkDTO();
+ dto.setTimestamp(new Date(target.getTimestamp()));
+ dto.setMillis(target.getTimestamp());
+ dto.setFlowFileUuid(edge.getUuid());
+ dto.setSourceId(source.getIdentifier());
+ dto.setTargetId(target.getIdentifier());
+ return dto;
+ }
+
+ /**
+ * Creates a LineageDTO for the specified Lineage.
+ *
+ * @param computeLineageSubmission submission
+ * @return dto
+ */
+ public LineageDTO createLineageDto(final ComputeLineageSubmission computeLineageSubmission) {
+ // build the lineage dto
+ final LineageDTO dto = new LineageDTO();
+ final LineageRequestDTO requestDto = new LineageRequestDTO();
+ final LineageResultsDTO resultsDto = new LineageResultsDTO();
+
+ // include the original request and results
+ dto.setRequest(requestDto);
+ dto.setResults(resultsDto);
+
+ // rebuild the request from the submission object
+ switch (computeLineageSubmission.getLineageComputationType()) {
+ case EXPAND_CHILDREN:
+ requestDto.setEventId(computeLineageSubmission.getExpandedEventId());
+ requestDto.setLineageRequestType(LineageRequestType.CHILDREN);
+ break;
+ case EXPAND_PARENTS:
+ requestDto.setEventId(computeLineageSubmission.getExpandedEventId());
+ requestDto.setLineageRequestType(LineageRequestType.PARENTS);
+ break;
+ case FLOWFILE_LINEAGE:
+ final Collection<String> uuids = computeLineageSubmission.getLineageFlowFileUuids();
+ if (uuids.size() == 1) {
+ requestDto.setUuid(uuids.iterator().next());
+ }
+ requestDto.setEventId(computeLineageSubmission.getExpandedEventId());
+ requestDto.setLineageRequestType(LineageRequestType.FLOWFILE);
+ break;
+ }
+
+ // include lineage details
+ dto.setId(computeLineageSubmission.getLineageIdentifier());
+ dto.setSubmissionTime(computeLineageSubmission.getSubmissionTime());
+
+ // create the results dto
+ final ComputeLineageResult results = computeLineageSubmission.getResult();
+ dto.setFinished(results.isFinished());
+ dto.setPercentCompleted(results.getPercentComplete());
+ dto.setExpiration(results.getExpiration());
+
+ final List<LineageNode> nodes = results.getNodes();
+ final List<LineageEdge> edges = results.getEdges();
+
+ final List<ProvenanceNodeDTO> nodeDtos = new ArrayList<>();
+ if (results.isFinished()) {
+ // create the node dto's
+ for (final LineageNode node : nodes) {
+ switch (node.getNodeType()) {
+ case FLOWFILE_NODE:
+ nodeDtos.add(createFlowFileNodeDTO(node));
+ break;
+ case PROVENANCE_EVENT_NODE:
+ nodeDtos.add(createProvenanceEventNodeDTO((ProvenanceEventLineageNode) node));
+ break;
+ }
+ }
+ }
+ resultsDto.setNodes(nodeDtos);
+
+ // include any errors
+ if (results.getError() != null) {
+ final Set<String> errors = new HashSet<>();
+ errors.add(results.getError());
+ resultsDto.setErrors(errors);
+ }
+
+ // create the link dto's
+ final List<ProvenanceLinkDTO> linkDtos = new ArrayList<>();
+ for (final LineageEdge edge : edges) {
+ linkDtos.add(createProvenanceLinkDTO(edge));
+ }
+ resultsDto.setLinks(linkDtos);
+
+ return dto;
+ }
+
+ /**
+ * Creates a SystemDiagnosticsDTO for the specified SystemDiagnostics.
+ *
+ * @param sysDiagnostics diags
+ * @return dto
+ */
+ public SystemDiagnosticsDTO createSystemDiagnosticsDto(final SystemDiagnostics sysDiagnostics) {
+
+ final SystemDiagnosticsDTO dto = new SystemDiagnosticsDTO();
+ final SystemDiagnosticsSnapshotDTO snapshot = new SystemDiagnosticsSnapshotDTO();
+ dto.setAggregateSnapshot(snapshot);
+
+ snapshot.setStatsLastRefreshed(new Date(sysDiagnostics.getCreationTimestamp()));
+
+ // processors
+ snapshot.setAvailableProcessors(sysDiagnostics.getAvailableProcessors());
+ snapshot.setProcessorLoadAverage(sysDiagnostics.getProcessorLoadAverage());
+
+ // threads
+ snapshot.setDaemonThreads(sysDiagnostics.getDaemonThreads());
+ snapshot.setTotalThreads(sysDiagnostics.getTotalThreads());
+
+ // heap
+ snapshot.setMaxHeap(FormatUtils.formatDataSize(sysDiagnostics.getMaxHeap()));
+ snapshot.setMaxHeapBytes(sysDiagnostics.getMaxHeap());
+ snapshot.setTotalHeap(FormatUtils.formatDataSize(sysDiagnostics.getTotalHeap()));
+ snapshot.setTotalHeapBytes(sysDiagnostics.getTotalHeap());
+ snapshot.setUsedHeap(FormatUtils.formatDataSize(sysDiagnostics.getUsedHeap()));
+ snapshot.setUsedHeapBytes(sysDiagnostics.getUsedHeap());
+ snapshot.setFreeHeap(FormatUtils.formatDataSize(sysDiagnostics.getFreeHeap()));
+ snapshot.setFreeHeapBytes(sysDiagnostics.getFreeHeap());
+ if (sysDiagnostics.getHeapUtilization() != -1) {
+ snapshot.setHeapUtilization(FormatUtils.formatUtilization(sysDiagnostics.getHeapUtilization()));
+ }
+
+ // non heap
+ snapshot.setMaxNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getMaxNonHeap()));
+ snapshot.setMaxNonHeapBytes(sysDiagnostics.getMaxNonHeap());
+ snapshot.setTotalNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getTotalNonHeap()));
+ snapshot.setTotalNonHeapBytes(sysDiagnostics.getTotalNonHeap());
+ snapshot.setUsedNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getUsedNonHeap()));
+ snapshot.setUsedNonHeapBytes(sysDiagnostics.getUsedNonHeap());
+ snapshot.setFreeNonHeap(FormatUtils.formatDataSize(sysDiagnostics.getFreeNonHeap()));
+ snapshot.setFreeNonHeapBytes(sysDiagnostics.getFreeNonHeap());
+ if (sysDiagnostics.getNonHeapUtilization() != -1) {
+ snapshot.setNonHeapUtilization(FormatUtils.formatUtilization(sysDiagnostics.getNonHeapUtilization()));
+ }
+
+ // flow file disk usage
+ final SystemDiagnosticsSnapshotDTO.StorageUsageDTO flowFileRepositoryStorageUsageDto = createStorageUsageDTO(null, sysDiagnostics.getFlowFileRepositoryStorageUsage());
+ snapshot.setFlowFileRepositoryStorageUsage(flowFileRepositoryStorageUsageDto);
+
+ // content disk usage
+ final Set<SystemDiagnosticsSnapshotDTO.StorageUsageDTO> contentRepositoryStorageUsageDtos = new LinkedHashSet<>();
+ snapshot.setContentRepositoryStorageUsage(contentRepositoryStorageUsageDtos);
+ for (final Map.Entry<String, StorageUsage> entry : sysDiagnostics.getContentRepositoryStorageUsage().entrySet()) {
+ contentRepositoryStorageUsageDtos.add(createStorageUsageDTO(entry.getKey(), entry.getValue()));
+ }
+
+ // provenance disk usage
+ final Set<SystemDiagnosticsSnapshotDTO.StorageUsageDTO> provenanceRepositoryStorageUsageDtos = new LinkedHashSet<>();
+ snapshot.setProvenanceRepositoryStorageUsage(provenanceRepositoryStorageUsageDtos);
+ for (final Map.Entry<String, StorageUsage> entry : sysDiagnostics.getProvenanceRepositoryStorageUsage().entrySet()) {
+ provenanceRepositoryStorageUsageDtos.add(createStorageUsageDTO(entry.getKey(), entry.getValue()));
+ }
+
+ // garbage collection
+ final Set<SystemDiagnosticsSnapshotDTO.GarbageCollectionDTO> garbageCollectionDtos = new LinkedHashSet<>();
+ snapshot.setGarbageCollection(garbageCollectionDtos);
+ for (final Map.Entry<String, GarbageCollection> entry : sysDiagnostics.getGarbageCollection().entrySet()) {
+ garbageCollectionDtos.add(createGarbageCollectionDTO(entry.getKey(), entry.getValue()));
+ }
+
+ // version info
+ final SystemDiagnosticsSnapshotDTO.VersionInfoDTO versionInfoDto = createVersionInfoDTO();
+ snapshot.setVersionInfo(versionInfoDto);
+
+ // uptime
+ snapshot.setUptime(FormatUtils.formatHoursMinutesSeconds(sysDiagnostics.getUptime(), TimeUnit.MILLISECONDS));
+
+ return dto;
+ }
+
+ /**
+ * Creates a StorageUsageDTO from the specified StorageUsage.
+ *
+ * @param identifier id
+ * @param storageUsage usage
+ * @return dto
+ */
+ public SystemDiagnosticsSnapshotDTO.StorageUsageDTO createStorageUsageDTO(final String identifier, final StorageUsage storageUsage) {
+ final SystemDiagnosticsSnapshotDTO.StorageUsageDTO dto = new SystemDiagnosticsSnapshotDTO.StorageUsageDTO();
+ dto.setIdentifier(identifier);
+ dto.setFreeSpace(FormatUtils.formatDataSize(storageUsage.getFreeSpace()));
+ dto.setTotalSpace(FormatUtils.formatDataSize(storageUsage.getTotalSpace()));
+ dto.setUsedSpace(FormatUtils.formatDataSize(storageUsage.getUsedSpace()));
+ dto.setFreeSpaceBytes(storageUsage.getFreeSpace());
+ dto.setTotalSpaceBytes(storageUsage.getTotalSpace());
+ dto.setUsedSpaceBytes(storageUsage.getUsedSpace());
+ dto.setUtilization(FormatUtils.formatUtilization(storageUsage.getDiskUtilization()));
+ return dto;
+ }
+
+ /**
+ * Creates a GarbageCollectionDTO from the specified GarbageCollection.
+ *
+ * @param name name
+ * @param garbageCollection gc
+ * @return dto
+ */
+ public SystemDiagnosticsSnapshotDTO.GarbageCollectionDTO createGarbageCollectionDTO(final String name, final GarbageCollection garbageCollection) {
+ final SystemDiagnosticsSnapshotDTO.GarbageCollectionDTO dto = new SystemDiagnosticsSnapshotDTO.GarbageCollectionDTO();
+ dto.setName(name);
+ dto.setCollectionCount(garbageCollection.getCollectionCount());
+ dto.setCollectionTime(FormatUtils.formatHoursMinutesSeconds(garbageCollection.getCollectionTime(), TimeUnit.MILLISECONDS));
+ dto.setCollectionMillis(garbageCollection.getCollectionTime());
+ return dto;
+ }
+
+ public SystemDiagnosticsSnapshotDTO.VersionInfoDTO createVersionInfoDTO() {
+ final SystemDiagnosticsSnapshotDTO.VersionInfoDTO dto = new SystemDiagnosticsSnapshotDTO.VersionInfoDTO();
+ dto.setJavaVendor(System.getProperty("java.vendor"));
+ dto.setJavaVersion(System.getProperty("java.version"));
+ dto.setOsName(System.getProperty("os.name"));
+ dto.setOsVersion(System.getProperty("os.version"));
+ dto.setOsArchitecture(System.getProperty("os.arch"));
+
+ final Bundle frameworkBundle = NarClassLoadersHolder.getInstance().getFrameworkBundle();
+ if (frameworkBundle != null) {
+ final BundleDetails frameworkDetails = frameworkBundle.getBundleDetails();
+
+ dto.setNiFiVersion(frameworkDetails.getCoordinate().getVersion());
+
+ // Get build info
+ dto.setBuildTag(frameworkDetails.getBuildTag());
+ dto.setBuildRevision(frameworkDetails.getBuildRevision());
+ dto.setBuildBranch(frameworkDetails.getBuildBranch());
+ dto.setBuildTimestamp(frameworkDetails.getBuildTimestampDate());
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates a ResourceDTO from the specified Resource.
+ *
+ * @param resource resource
+ * @return dto
+ */
+ public ResourceDTO createResourceDto(final Resource resource) {
+ final ResourceDTO dto = new ResourceDTO();
+ dto.setIdentifier(resource.getIdentifier());
+ dto.setName(resource.getName());
+ return dto;
+ }
+
+ /**
+ * Creates a ProcessorDiagnosticsDTO from the given Processor and status information with some additional supporting information
+ *
+ * @param procNode the processor to create diagnostics for
+ * @param procStatus the status of given processor
+ * @param bulletinRepo the bulletin repository
+ * @param flowController flowController
+ * @param serviceEntityFactory function for creating a ControllerServiceEntity from a given ID
+ * @return ProcessorDiagnosticsDTO for the given Processor
+ */
+ public ProcessorDiagnosticsDTO createProcessorDiagnosticsDto(final ProcessorNode procNode, final ProcessorStatus procStatus, final BulletinRepository bulletinRepo,
+ final FlowController flowController, final Function<String, ControllerServiceEntity> serviceEntityFactory) {
+
+ final ProcessorDiagnosticsDTO procDiagnostics = new ProcessorDiagnosticsDTO();
+
+ procDiagnostics.setClassLoaderDiagnostics(createClassLoaderDiagnosticsDto(procNode));
+ procDiagnostics.setIncomingConnections(procNode.getIncomingConnections().stream()
+ .map(this::createConnectionDiagnosticsDto)
+ .collect(Collectors.toSet()));
+ procDiagnostics.setOutgoingConnections(procNode.getConnections().stream()
+ .map(this::createConnectionDiagnosticsDto)
+ .collect(Collectors.toSet()));
+ procDiagnostics.setJvmDiagnostics(createJvmDiagnosticsDto(flowController));
+ procDiagnostics.setProcessor(createProcessorDto(procNode));
+ procDiagnostics.setProcessorStatus(createProcessorStatusDto(procStatus));
+ procDiagnostics.setThreadDumps(createThreadDumpDtos(procNode));
+
+ final Set<ControllerServiceDiagnosticsDTO> referencedServiceDiagnostics = createReferencedServiceDiagnostics(procNode.getProperties(),
+ flowController.getControllerServiceProvider(), serviceEntityFactory);
+ procDiagnostics.setReferencedControllerServices(referencedServiceDiagnostics);
+
+ return procDiagnostics;
+ }
+
+ private Set<ControllerServiceDiagnosticsDTO> createReferencedServiceDiagnostics(final Map<PropertyDescriptor, String> properties, final ControllerServiceProvider serviceProvider,
+ final Function<String, ControllerServiceEntity> serviceEntityFactory) {
+
+ final Set<ControllerServiceDiagnosticsDTO> referencedServiceDiagnostics = new HashSet<>();
+ for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+ if (descriptor.getControllerServiceDefinition() == null) {
+ continue;
+ }
+
+ final String serviceId = entry.getValue();
+ if (serviceId == null) {
+ continue;
+ }
+
+ final ControllerServiceNode serviceNode = serviceProvider.getControllerServiceNode(serviceId);
+ if (serviceNode == null) {
+ continue;
+ }
+
+ final ControllerServiceDiagnosticsDTO serviceDiagnostics = createControllerServiceDiagnosticsDto(serviceNode, serviceEntityFactory, serviceProvider);
+ if (serviceDiagnostics != null) {
+ referencedServiceDiagnostics.add(serviceDiagnostics);
+ }
+ }
+
+ return referencedServiceDiagnostics;
+ }
+
+ /**
+ * Creates a ControllerServiceDiagnosticsDTO from the given Controller Service with some additional supporting information
+ *
+ * @param serviceNode the controller service to create diagnostics for
+ * @param serviceEntityFactory a function to convert a controller service id to a controller service entity
+ * @param serviceProvider the controller service provider
+ * @return ControllerServiceDiagnosticsDTO for the given Controller Service
+ */
+ public ControllerServiceDiagnosticsDTO createControllerServiceDiagnosticsDto(final ControllerServiceNode serviceNode, final Function<String, ControllerServiceEntity> serviceEntityFactory,
+ final ControllerServiceProvider serviceProvider) {
+
+ final ControllerServiceDiagnosticsDTO serviceDiagnostics = new ControllerServiceDiagnosticsDTO();
+ final ControllerServiceEntity serviceEntity = serviceEntityFactory.apply(serviceNode.getIdentifier());
+ serviceDiagnostics.setControllerService(serviceEntity);
+
+ serviceDiagnostics.setClassLoaderDiagnostics(createClassLoaderDiagnosticsDto(serviceNode));
+ return serviceDiagnostics;
+ }
+
+
+ private ClassLoaderDiagnosticsDTO createClassLoaderDiagnosticsDto(final ControllerServiceNode serviceNode) {
+ ClassLoader componentClassLoader = extensionManager.getInstanceClassLoader(serviceNode.getIdentifier());
+ if (componentClassLoader == null) {
+ componentClassLoader = serviceNode.getControllerServiceImplementation().getClass().getClassLoader();
+ }
+
+ return createClassLoaderDiagnosticsDto(componentClassLoader);
+ }
+
+
+ private ClassLoaderDiagnosticsDTO createClassLoaderDiagnosticsDto(final ProcessorNode procNode) {
+ ClassLoader componentClassLoader = extensionManager.getInstanceClassLoader(procNode.getIdentifier());
+ if (componentClassLoader == null) {
+ componentClassLoader = procNode.getProcessor().getClass().getClassLoader();
+ }
+
+ return createClassLoaderDiagnosticsDto(componentClassLoader);
+ }
+
+ private ClassLoaderDiagnosticsDTO createClassLoaderDiagnosticsDto(final ClassLoader classLoader) {
+ final ClassLoaderDiagnosticsDTO dto = new ClassLoaderDiagnosticsDTO();
+
+ final Bundle bundle = extensionManager.getBundle(classLoader);
+ if (bundle != null) {
+ dto.setBundle(createBundleDto(bundle.getBundleDetails().getCoordinate()));
+ }
+
+ final ClassLoader parentClassLoader = classLoader.getParent();
+ if (parentClassLoader != null) {
+ dto.setParentClassLoader(createClassLoaderDiagnosticsDto(parentClassLoader));
+ }
+
+ return dto;
+ }
+
+
+ private ConnectionDiagnosticsDTO createConnectionDiagnosticsDto(final Connection connection) {
+ final ConnectionDiagnosticsDTO dto = new ConnectionDiagnosticsDTO();
+ dto.setConnection(createConnectionDto(connection));
+ dto.setAggregateSnapshot(createConnectionDiagnosticsSnapshotDto(connection));
+ return dto;
+ }
+
+ private ConnectionDiagnosticsSnapshotDTO createConnectionDiagnosticsSnapshotDto(final Connection connection) {
+ final ConnectionDiagnosticsSnapshotDTO dto = new ConnectionDiagnosticsSnapshotDTO();
+
+ final QueueDiagnostics queueDiagnostics = connection.getFlowFileQueue().getQueueDiagnostics();
+
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+ final QueueSize totalSize = queue.size();
+ dto.setTotalByteCount(totalSize.getByteCount());
+ dto.setTotalFlowFileCount(totalSize.getObjectCount());
+
+ final LocalQueuePartitionDiagnostics localDiagnostics = queueDiagnostics.getLocalQueuePartitionDiagnostics();
+ dto.setLocalQueuePartition(createLocalQueuePartitionDto(localDiagnostics));
+
+ final List<RemoteQueuePartitionDiagnostics> remoteDiagnostics = queueDiagnostics.getRemoteQueuePartitionDiagnostics();
+ if (remoteDiagnostics != null) {
+ final List<RemoteQueuePartitionDTO> remoteDiagnosticsDtos = remoteDiagnostics.stream()
+ .map(this::createRemoteQueuePartitionDto)
+ .collect(Collectors.toList());
+
+ dto.setRemoteQueuePartitions(remoteDiagnosticsDtos);
+ }
+
+ return dto;
+ }
+
+ private LocalQueuePartitionDTO createLocalQueuePartitionDto(final LocalQueuePartitionDiagnostics queueDiagnostics) {
+ final LocalQueuePartitionDTO dto = new LocalQueuePartitionDTO();
+
+ final QueueSize activeSize = queueDiagnostics.getActiveQueueSize();
+ dto.setActiveQueueByteCount(activeSize.getByteCount());
+ dto.setActiveQueueFlowFileCount(activeSize.getObjectCount());
+
+ final QueueSize inFlightSize = queueDiagnostics.getUnacknowledgedQueueSize();
+ dto.setInFlightByteCount(inFlightSize.getByteCount());
+ dto.setInFlightFlowFileCount(inFlightSize.getObjectCount());
+
+ final QueueSize swapSize = queueDiagnostics.getSwapQueueSize();
+ dto.setSwapByteCount(swapSize.getByteCount());
+ dto.setSwapFlowFileCount(swapSize.getObjectCount());
+ dto.setSwapFiles(queueDiagnostics.getSwapFileCount());
+
+ dto.setTotalByteCount(activeSize.getByteCount() + inFlightSize.getByteCount() + swapSize.getByteCount());
+ dto.setTotalFlowFileCount(activeSize.getObjectCount() + inFlightSize.getObjectCount() + swapSize.getObjectCount());
+
+ dto.setAllActiveQueueFlowFilesPenalized(queueDiagnostics.isAllActiveFlowFilesPenalized());
+ dto.setAnyActiveQueueFlowFilesPenalized(queueDiagnostics.isAnyActiveFlowFilePenalized());
+
+ return dto;
+ }
+
+ private RemoteQueuePartitionDTO createRemoteQueuePartitionDto(final RemoteQueuePartitionDiagnostics queueDiagnostics) {
+ final RemoteQueuePartitionDTO dto = new RemoteQueuePartitionDTO();
+
+ dto.setNodeIdentifier(queueDiagnostics.getNodeIdentifier());
+
+ final QueueSize activeSize = queueDiagnostics.getActiveQueueSize();
+ dto.setActiveQueueByteCount(activeSize.getByteCount());
+ dto.setActiveQueueFlowFileCount(activeSize.getObjectCount());
+
+ final QueueSize inFlightSize = queueDiagnostics.getUnacknowledgedQueueSize();
+ dto.setInFlightByteCount(inFlightSize.getByteCount());
+ dto.setInFlightFlowFileCount(inFlightSize.getObjectCount());
+
+ final QueueSize swapSize = queueDiagnostics.getSwapQueueSize();
+ dto.setSwapByteCount(swapSize.getByteCount());
+ dto.setSwapFlowFileCount(swapSize.getObjectCount());
+ dto.setSwapFiles(queueDiagnostics.getSwapFileCount());
+
+ dto.setTotalByteCount(activeSize.getByteCount() + inFlightSize.getByteCount() + swapSize.getByteCount());
+ dto.setTotalFlowFileCount(activeSize.getObjectCount() + inFlightSize.getObjectCount() + swapSize.getObjectCount());
+
+ return dto;
+ }
+
+ private JVMDiagnosticsDTO createJvmDiagnosticsDto(final FlowController flowController) {
+ final JVMDiagnosticsDTO dto = new JVMDiagnosticsDTO();
+ dto.setAggregateSnapshot(createJvmDiagnosticsSnapshotDto(flowController));
+ dto.setClustered(flowController.isClustered());
+ dto.setConnected(flowController.isConnected());
+ return dto;
+ }
+
+ private JVMDiagnosticsSnapshotDTO createJvmDiagnosticsSnapshotDto(final FlowController flowController) {
+ final JVMDiagnosticsSnapshotDTO dto = new JVMDiagnosticsSnapshotDTO();
+
+ final JVMControllerDiagnosticsSnapshotDTO controllerDiagnosticsDto = new JVMControllerDiagnosticsSnapshotDTO();
+ final JVMFlowDiagnosticsSnapshotDTO flowDiagnosticsDto = new JVMFlowDiagnosticsSnapshotDTO();
+ final JVMSystemDiagnosticsSnapshotDTO systemDiagnosticsDto = new JVMSystemDiagnosticsSnapshotDTO();
+
+ dto.setControllerDiagnostics(controllerDiagnosticsDto);
+ dto.setFlowDiagnosticsDto(flowDiagnosticsDto);
+ dto.setSystemDiagnosticsDto(systemDiagnosticsDto);
+
+ final SystemDiagnostics systemDiagnostics = flowController.getSystemDiagnostics();
+
+ // flow-related information
+ final Set<BundleDTO> bundlesLoaded = extensionManager.getAllBundles().stream()
+ .map(bundle -> bundle.getBundleDetails().getCoordinate())
+ .sorted((a, b) -> a.getCoordinate().compareTo(b.getCoordinate()))
+ .map(this::createBundleDto)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ flowDiagnosticsDto.setActiveEventDrivenThreads(flowController.getActiveEventDrivenThreadCount());
+ flowDiagnosticsDto.setActiveTimerDrivenThreads(flowController.getActiveTimerDrivenThreadCount());
+ flowDiagnosticsDto.setBundlesLoaded(bundlesLoaded);
+ flowDiagnosticsDto.setTimeZone(System.getProperty("user.timezone"));
+ flowDiagnosticsDto.setUptime(FormatUtils.formatHoursMinutesSeconds(systemDiagnostics.getUptime(), TimeUnit.MILLISECONDS));
+
+ // controller-related information
+ controllerDiagnosticsDto.setClusterCoordinator(flowController.isClusterCoordinator());
+ controllerDiagnosticsDto.setPrimaryNode(flowController.isPrimary());
+ controllerDiagnosticsDto.setMaxEventDrivenThreads(flowController.getMaxEventDrivenThreadCount());
+ controllerDiagnosticsDto.setMaxTimerDrivenThreads(flowController.getMaxTimerDrivenThreadCount());
+
+ // system-related information
+ systemDiagnosticsDto.setMaxOpenFileDescriptors(systemDiagnostics.getMaxOpenFileHandles());
+ systemDiagnosticsDto.setOpenFileDescriptors(systemDiagnostics.getOpenFileHandles());
+ systemDiagnosticsDto.setPhysicalMemoryBytes(systemDiagnostics.getTotalPhysicalMemory());
+ systemDiagnosticsDto.setPhysicalMemory(FormatUtils.formatDataSize(systemDiagnostics.getTotalPhysicalMemory()));
+
+ final NumberFormat percentageFormat = NumberFormat.getPercentInstance();
+ percentageFormat.setMaximumFractionDigits(2);
+
+ final Set<RepositoryUsageDTO> contentRepoUsage = new HashSet<>();
+ for (final Map.Entry<String, StorageUsage> entry : systemDiagnostics.getContentRepositoryStorageUsage().entrySet()) {
+ final String repoName = entry.getKey();
+ final StorageUsage usage = entry.getValue();
+
+ final RepositoryUsageDTO usageDto = new RepositoryUsageDTO();
+ usageDto.setName(repoName);
+
+ usageDto.setFileStoreHash(DigestUtils.sha256Hex(flowController.getContentRepoFileStoreName(repoName)));
+ usageDto.setFreeSpace(FormatUtils.formatDataSize(usage.getFreeSpace()));
+ usageDto.setFreeSpaceBytes(usage.getFreeSpace());
+ usageDto.setTotalSpace(FormatUtils.formatDataSize(usage.getTotalSpace()));
+ usageDto.setTotalSpaceBytes(usage.getTotalSpace());
+
+ final double usedPercentage = (usage.getTotalSpace() - usage.getFreeSpace()) / (double) usage.getTotalSpace();
+ final String utilization = percentageFormat.format(usedPercentage);
+ usageDto.setUtilization(utilization);
+ contentRepoUsage.add(usageDto);
+ }
+
+ final Set<RepositoryUsageDTO> provRepoUsage = new HashSet<>();
+ for (final Map.Entry<String, StorageUsage> entry : systemDiagnostics.getProvenanceRepositoryStorageUsage().entrySet()) {
+ final String repoName = entry.getKey();
+ final StorageUsage usage = entry.getValue();
+
+ final RepositoryUsageDTO usageDto = new RepositoryUsageDTO();
+ usageDto.setName(repoName);
+
+ usageDto.setFileStoreHash(DigestUtils.sha256Hex(flowController.getProvenanceRepoFileStoreName(repoName)));
+ usageDto.setFreeSpace(FormatUtils.formatDataSize(usage.getFreeSpace()));
+ usageDto.setFreeSpaceBytes(usage.getFreeSpace());
+ usageDto.setTotalSpace(FormatUtils.formatDataSize(usage.getTotalSpace()));
+ usageDto.setTotalSpaceBytes(usage.getTotalSpace());
+
+ final double usedPercentage = (usage.getTotalSpace() - usage.getFreeSpace()) / (double) usage.getTotalSpace();
+ final String utilization = percentageFormat.format(usedPercentage);
+ usageDto.setUtilization(utilization);
+ provRepoUsage.add(usageDto);
+ }
+
+ final RepositoryUsageDTO flowFileRepoUsage = new RepositoryUsageDTO();
+ for (final Map.Entry<String, StorageUsage> entry : systemDiagnostics.getProvenanceRepositoryStorageUsage().entrySet()) {
+ final String repoName = entry.getKey();
+ final StorageUsage usage = entry.getValue();
+
+ flowFileRepoUsage.setName(repoName);
+
+ flowFileRepoUsage.setFileStoreHash(DigestUtils.sha256Hex(flowController.getFlowRepoFileStoreName()));
+ flowFileRepoUsage.setFreeSpace(FormatUtils.formatDataSize(usage.getFreeSpace()));
+ flowFileRepoUsage.setFreeSpaceBytes(usage.getFreeSpace());
+ flowFileRepoUsage.setTotalSpace(FormatUtils.formatDataSize(usage.getTotalSpace()));
+ flowFileRepoUsage.setTotalSpaceBytes(usage.getTotalSpace());
+
+ final double usedPercentage = (usage.getTotalSpace() - usage.getFreeSpace()) / (double) usage.getTotalSpace();
+ final String utilization = percentageFormat.format(usedPercentage);
+ flowFileRepoUsage.setUtilization(utilization);
+ }
+
+ systemDiagnosticsDto.setContentRepositoryStorageUsage(contentRepoUsage);
+ systemDiagnosticsDto.setCpuCores(systemDiagnostics.getAvailableProcessors());
+ systemDiagnosticsDto.setCpuLoadAverage(systemDiagnostics.getProcessorLoadAverage());
+ systemDiagnosticsDto.setFlowFileRepositoryStorageUsage(flowFileRepoUsage);
+ systemDiagnosticsDto.setMaxHeapBytes(systemDiagnostics.getMaxHeap());
+ systemDiagnosticsDto.setMaxHeap(FormatUtils.formatDataSize(systemDiagnostics.getMaxHeap()));
+ systemDiagnosticsDto.setProvenanceRepositoryStorageUsage(provRepoUsage);
+
+ // Create the Garbage Collection History info
+ final GarbageCollectionHistory gcHistory = flowController.getGarbageCollectionHistory();
+ final List<GarbageCollectionDiagnosticsDTO> gcDiagnostics = new ArrayList<>();
+ for (final String memoryManager : gcHistory.getMemoryManagerNames()) {
+ final List<GarbageCollectionStatus> statuses = gcHistory.getGarbageCollectionStatuses(memoryManager);
+
+ final List<GCDiagnosticsSnapshotDTO> gcSnapshots = new ArrayList<>();
+ for (final GarbageCollectionStatus status : statuses) {
+ final GCDiagnosticsSnapshotDTO snapshotDto = new GCDiagnosticsSnapshotDTO();
+ snapshotDto.setTimestamp(status.getTimestamp());
+ snapshotDto.setCollectionCount(status.getCollectionCount());
+ snapshotDto.setCollectionMillis(status.getCollectionMillis());
+ gcSnapshots.add(snapshotDto);
+ }
+
+ gcSnapshots.sort(Comparator.comparing(GCDiagnosticsSnapshotDTO::getTimestamp).reversed());
+
+ final GarbageCollectionDiagnosticsDTO gcDto = new GarbageCollectionDiagnosticsDTO();
+ gcDto.setMemoryManagerName(memoryManager);
+ gcDto.setSnapshots(gcSnapshots);
+ gcDiagnostics.add(gcDto);
+ }
+
+ systemDiagnosticsDto.setGarbageCollectionDiagnostics(gcDiagnostics);
+
+ return dto;
+ }
+
+ private List<ThreadDumpDTO> createThreadDumpDtos(final ProcessorNode procNode) {
+ final List<ThreadDumpDTO> threadDumps = new ArrayList<>();
+
+ final List<ActiveThreadInfo> activeThreads = procNode.getActiveThreads();
+ for (final ActiveThreadInfo threadInfo : activeThreads) {
+ final ThreadDumpDTO dto = new ThreadDumpDTO();
+ dto.setStackTrace(threadInfo.getStackTrace());
+ dto.setThreadActiveMillis(threadInfo.getActiveMillis());
+ dto.setThreadName(threadInfo.getThreadName());
+ dto.setTaskTerminated(threadInfo.isTerminated());
+ threadDumps.add(dto);
+ }
+
+ return threadDumps;
+ }
+
+ /**
+ * Creates a ProcessorConfigDTO from the specified ProcessorNode.
+ *
+ * @param procNode node
+ * @return dto
+ */
+ public ProcessorConfigDTO createProcessorConfigDto(final ProcessorNode procNode) {
+ if (procNode == null) {
+ return null;
+ }
+
+ final ProcessorConfigDTO dto = new ProcessorConfigDTO();
+
+ // sort a copy of the properties
+ final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+ @Override
+ public int compare(final PropertyDescriptor o1, final PropertyDescriptor o2) {
+ return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+ }
+ });
+ sortedProperties.putAll(procNode.getProperties());
+
+ // get the property order from the processor
+ final Processor processor = procNode.getProcessor();
+ final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+ final List<PropertyDescriptor> descriptors = processor.getPropertyDescriptors();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ for (final PropertyDescriptor descriptor : descriptors) {
+ orderedProperties.put(descriptor, null);
+ }
+ }
+ orderedProperties.putAll(sortedProperties);
+
+ // build the descriptor and property dtos
+ dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+ dto.setProperties(new LinkedHashMap<String, String>());
+ for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+
+ // store the property descriptor
+ dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor, procNode.getProcessGroup().getIdentifier()));
+
+ // determine the property value - don't include sensitive properties
+ String propertyValue = entry.getValue();
+ if (propertyValue != null && descriptor.isSensitive()) {
+ propertyValue = SENSITIVE_VALUE_MASK;
+ } else if (propertyValue == null && descriptor.getDefaultValue() != null) {
+ propertyValue = descriptor.getDefaultValue();
+ }
+
+ // set the property value
+ dto.getProperties().put(descriptor.getName(), propertyValue);
+ }
+
+ dto.setSchedulingPeriod(procNode.getSchedulingPeriod());
+ dto.setPenaltyDuration(procNode.getPenalizationPeriod());
+ dto.setYieldDuration(procNode.getYieldPeriod());
+ dto.setRunDurationMillis(procNode.getRunDuration(TimeUnit.MILLISECONDS));
+ dto.setConcurrentlySchedulableTaskCount(procNode.getMaxConcurrentTasks());
+ dto.setLossTolerant(procNode.isLossTolerant());
+ dto.setComments(procNode.getComments());
+ dto.setBulletinLevel(procNode.getBulletinLevel().name());
+ dto.setSchedulingStrategy(procNode.getSchedulingStrategy().name());
+ dto.setExecutionNode(procNode.getExecutionNode().name());
+ dto.setAnnotationData(procNode.getAnnotationData());
+
+ // set up the default values for concurrent tasks and scheduling period
+ final Map<String, String> defaultConcurrentTasks = new HashMap<>();
+ defaultConcurrentTasks.put(SchedulingStrategy.TIMER_DRIVEN.name(), String.valueOf(SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks()));
+ defaultConcurrentTasks.put(SchedulingStrategy.EVENT_DRIVEN.name(), String.valueOf(SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks()));
+ defaultConcurrentTasks.put(SchedulingStrategy.CRON_DRIVEN.name(), String.valueOf(SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks()));
+ dto.setDefaultConcurrentTasks(defaultConcurrentTasks);
+
+ final Map<String, String> defaultSchedulingPeriod = new HashMap<>();
+ defaultSchedulingPeriod.put(SchedulingStrategy.TIMER_DRIVEN.name(), SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+ defaultSchedulingPeriod.put(SchedulingStrategy.CRON_DRIVEN.name(), SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+ dto.setDefaultSchedulingPeriod(defaultSchedulingPeriod);
+
+ return dto;
+ }
+
+ /**
+ * Creates a PropertyDesriptorDTO from the specified PropertyDesriptor.
+ *
+ * @param propertyDescriptor descriptor
+ * @param groupId the Identifier of the Process Group that the component belongs to
+ * @return dto
+ */
+ public PropertyDescriptorDTO createPropertyDescriptorDto(final PropertyDescriptor propertyDescriptor, final String groupId) {
+ if (propertyDescriptor == null) {
+ return null;
+ }
+
+ final PropertyDescriptorDTO dto = new PropertyDescriptorDTO();
+
+ dto.setName(propertyDescriptor.getName());
+ dto.setDisplayName(propertyDescriptor.getDisplayName());
+ dto.setRequired(propertyDescriptor.isRequired());
+ dto.setSensitive(propertyDescriptor.isSensitive());
+ dto.setDynamic(propertyDescriptor.isDynamic());
+ dto.setDescription(propertyDescriptor.getDescription());
+ dto.setDefaultValue(propertyDescriptor.getDefaultValue());
+ dto.setSupportsEl(propertyDescriptor.isExpressionLanguageSupported());
+
+ // to support legacy/deprecated method .expressionLanguageSupported(true)
+ String description = propertyDescriptor.isExpressionLanguageSupported()
+ && propertyDescriptor.getExpressionLanguageScope().equals(ExpressionLanguageScope.NONE)
+ ? "true (undefined scope)" : propertyDescriptor.getExpressionLanguageScope().getDescription();
+ dto.setExpressionLanguageScope(description);
+
+ // set the identifies controller service is applicable
+ if (propertyDescriptor.getControllerServiceDefinition() != null) {
+ final Class serviceClass = propertyDescriptor.getControllerServiceDefinition();
+ final Bundle serviceBundle = extensionManager.getBundle(serviceClass.getClassLoader());
+
+ dto.setIdentifiesControllerService(serviceClass.getName());
+ dto.setIdentifiesControllerServiceBundle(createBundleDto(serviceBundle.getBundleDetails().getCoordinate()));
+ }
+
+ final Class<? extends ControllerService> serviceDefinition = propertyDescriptor.getControllerServiceDefinition();
+ if (propertyDescriptor.getAllowableValues() == null) {
+ if (serviceDefinition == null) {
+ dto.setAllowableValues(null);
+ } else {
+ final List<AllowableValueEntity> allowableValues = new ArrayList<>();
+ final List<String> controllerServiceIdentifiers = new ArrayList<>(controllerServiceProvider.getControllerServiceIdentifiers(serviceDefinition, groupId));
+ Collections.sort(controllerServiceIdentifiers, Collator.getInstance(Locale.US));
+ for (final String serviceIdentifier : controllerServiceIdentifiers) {
+ final ControllerServiceNode service = controllerServiceProvider.getControllerServiceNode(serviceIdentifier);
+ final boolean isServiceAuthorized = service.isAuthorized(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ final String displayName = isServiceAuthorized ? service.getName() : serviceIdentifier;
+
+ final AllowableValueDTO allowableValue = new AllowableValueDTO();
+ allowableValue.setDisplayName(displayName);
+ allowableValue.setValue(serviceIdentifier);
+ allowableValues.add(entityFactory.createAllowableValueEntity(allowableValue, isServiceAuthorized));
+ }
+ dto.setAllowableValues(allowableValues);
+ }
+ } else {
+ final List<AllowableValueEntity> allowableValues = new ArrayList<>();
+ for (final AllowableValue allowableValue : propertyDescriptor.getAllowableValues()) {
+ final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
+ allowableValueDto.setDisplayName(allowableValue.getDisplayName());
+ allowableValueDto.setValue(allowableValue.getValue());
+ allowableValueDto.setDescription(allowableValue.getDescription());
+ allowableValues.add(entityFactory.createAllowableValueEntity(allowableValueDto, true));
+ }
+
+ dto.setAllowableValues(allowableValues);
+ }
+
+ return dto;
+ }
+
+ // Copy methods
+ public LabelDTO copy(final LabelDTO original) {
+ final LabelDTO copy = new LabelDTO();
+ copy.setId(original.getId());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setLabel(original.getLabel());
+ copy.setStyle(copy(original.getStyle()));
+ copy.setPosition(original.getPosition());
+ copy.setWidth(original.getWidth());
+ copy.setHeight(original.getHeight());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ return copy;
+ }
+
+ public ControllerServiceDTO copy(final ControllerServiceDTO original) {
+ final ControllerServiceDTO copy = new ControllerServiceDTO();
+ copy.setAnnotationData(original.getAnnotationData());
+ copy.setControllerServiceApis(original.getControllerServiceApis());
+ copy.setComments(original.getComments());
+ copy.setCustomUiUrl(original.getCustomUiUrl());
+ copy.setDescriptors(copy(original.getDescriptors()));
+ copy.setId(original.getId());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setName(original.getName());
+ copy.setProperties(copy(original.getProperties()));
+ copy.setReferencingComponents(copy(original.getReferencingComponents()));
+ copy.setState(original.getState());
+ copy.setType(original.getType());
+ copy.setBundle(copy(original.getBundle()));
+ copy.setExtensionMissing(original.getExtensionMissing());
+ copy.setMultipleVersionsAvailable(original.getMultipleVersionsAvailable());
+ copy.setPersistsState(original.getPersistsState());
+ copy.setValidationErrors(copy(original.getValidationErrors()));
+ copy.setValidationStatus(original.getValidationStatus());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+ return copy;
+ }
+
+ public FunnelDTO copy(final FunnelDTO original) {
+ final FunnelDTO copy = new FunnelDTO();
+ copy.setId(original.getId());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setPosition(original.getPosition());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ return copy;
+ }
+
+ private <T> List<T> copy(final List<T> original) {
+ if (original == null) {
+ return null;
+ } else {
+ return new ArrayList<>(original);
+ }
+ }
+
+ private <T> List<T> copy(final Collection<T> original) {
+ if (original == null) {
+ return null;
+ } else {
+ return new ArrayList<>(original);
+ }
+ }
+
+ private <T> Set<T> copy(final Set<T> original) {
+ if (original == null) {
+ return null;
+ } else {
+ return new LinkedHashSet<>(original);
+ }
+ }
+
+ private <S, T> Map<S, T> copy(final Map<S, T> original) {
+ if (original == null) {
+ return null;
+ } else {
+ return new LinkedHashMap<>(original);
+ }
+ }
+
+ public BundleDTO copy(final BundleDTO original) {
+ if (original == null) {
+ return null;
+ }
+
+ final BundleDTO copy = new BundleDTO();
+ copy.setGroup(original.getGroup());
+ copy.setArtifact(original.getArtifact());
+ copy.setVersion(original.getVersion());
+ return copy;
+ }
+
+ public ProcessorDTO copy(final ProcessorDTO original) {
+ final ProcessorDTO copy = new ProcessorDTO();
+ copy.setConfig(copy(original.getConfig()));
+ copy.setPosition(original.getPosition());
+ copy.setId(original.getId());
+ copy.setName(original.getName());
+ copy.setDescription(original.getDescription());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setRelationships(copy(original.getRelationships()));
+ copy.setState(original.getState());
+ copy.setStyle(copy(original.getStyle()));
+ copy.setType(original.getType());
+ copy.setBundle(copy(original.getBundle()));
+ copy.setSupportsParallelProcessing(original.getSupportsParallelProcessing());
+ copy.setSupportsEventDriven(original.getSupportsEventDriven());
+ copy.setSupportsBatching(original.getSupportsBatching());
+ copy.setPersistsState(original.getPersistsState());
+ copy.setExecutionNodeRestricted(original.isExecutionNodeRestricted());
+ copy.setExtensionMissing(original.getExtensionMissing());
+ copy.setMultipleVersionsAvailable(original.getMultipleVersionsAvailable());
+ copy.setValidationErrors(copy(original.getValidationErrors()));
+ copy.setValidationStatus(original.getValidationStatus());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ return copy;
+ }
+
+ private ProcessorConfigDTO copy(final ProcessorConfigDTO original) {
+ final ProcessorConfigDTO copy = new ProcessorConfigDTO();
+ copy.setAnnotationData(original.getAnnotationData());
+ copy.setAutoTerminatedRelationships(copy(original.getAutoTerminatedRelationships()));
+ copy.setComments(original.getComments());
+ copy.setSchedulingStrategy(original.getSchedulingStrategy());
+ copy.setExecutionNode(original.getExecutionNode());
+ copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+ copy.setCustomUiUrl(original.getCustomUiUrl());
+ copy.setDescriptors(copy(original.getDescriptors()));
+ copy.setProperties(copy(original.getProperties()));
+ copy.setSchedulingPeriod(original.getSchedulingPeriod());
+ copy.setPenaltyDuration(original.getPenaltyDuration());
+ copy.setYieldDuration(original.getYieldDuration());
+ copy.setRunDurationMillis(original.getRunDurationMillis());
+ copy.setBulletinLevel(original.getBulletinLevel());
+ copy.setDefaultConcurrentTasks(original.getDefaultConcurrentTasks());
+ copy.setDefaultSchedulingPeriod(original.getDefaultSchedulingPeriod());
+ copy.setLossTolerant(original.isLossTolerant());
+
+ return copy;
+ }
+
+ public ConnectionDTO copy(final ConnectionDTO original) {
+ final ConnectionDTO copy = new ConnectionDTO();
+ copy.setAvailableRelationships(copy(original.getAvailableRelationships()));
+ copy.setDestination(original.getDestination());
+ copy.setPosition(original.getPosition());
+ copy.setId(original.getId());
+ copy.setName(original.getName());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setSelectedRelationships(copy(original.getSelectedRelationships()));
+ copy.setFlowFileExpiration(original.getFlowFileExpiration());
+ copy.setBackPressureObjectThreshold(original.getBackPressureObjectThreshold());
+ copy.setBackPressureDataSizeThreshold(original.getBackPressureDataSizeThreshold());
+ copy.setPrioritizers(copy(original.getPrioritizers()));
+ copy.setSource(original.getSource());
+ copy.setzIndex(original.getzIndex());
+ copy.setLabelIndex(original.getLabelIndex());
+ copy.setBends(copy(original.getBends()));
+ copy.setLoadBalancePartitionAttribute(original.getLoadBalancePartitionAttribute());
+ copy.setLoadBalanceStrategy(original.getLoadBalanceStrategy());
+ copy.setLoadBalanceCompression(original.getLoadBalanceCompression());
+ copy.setLoadBalanceStatus(original.getLoadBalanceStatus());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ return copy;
+ }
+
+ public BulletinDTO copy(final BulletinDTO original) {
+ final BulletinDTO copy = new BulletinDTO();
+ copy.setId(original.getId());
+ copy.setTimestamp(original.getTimestamp());
+ copy.setGroupId(original.getGroupId());
+ copy.setSourceId(original.getSourceId());
+ copy.setSourceName(original.getSourceName());
+ copy.setCategory(original.getCategory());
+ copy.setLevel(original.getLevel());
+ copy.setMessage(original.getMessage());
+ copy.setNodeAddress(original.getNodeAddress());
+ return copy;
+ }
+
+ public PortDTO copy(final PortDTO original) {
+ final PortDTO copy = new PortDTO();
+ copy.setPosition(original.getPosition());
+ copy.setId(original.getId());
+ copy.setName(original.getName());
+ copy.setComments(original.getComments());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setState(original.getState());
+ copy.setType(original.getType());
+ copy.setTransmitting(original.isTransmitting());
+ copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+ copy.setUserAccessControl(copy(original.getUserAccessControl()));
+ copy.setGroupAccessControl(copy(original.getGroupAccessControl()));
+ copy.setValidationErrors(copy(original.getValidationErrors()));
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+ return copy;
+ }
+
+ public RemoteProcessGroupPortDTO copy(final RemoteProcessGroupPortDTO original) {
+ final RemoteProcessGroupPortDTO copy = new RemoteProcessGroupPortDTO();
+ copy.setId(original.getId());
+ copy.setTargetId(original.getTargetId());
+ copy.setGroupId(original.getGroupId());
+ copy.setName(original.getName());
+ copy.setComments(original.getComments());
+ copy.setConnected(original.isConnected());
+ copy.setTargetRunning(original.isTargetRunning());
+ copy.setTransmitting(original.isTransmitting());
+ copy.setConcurrentlySchedulableTaskCount(original.getConcurrentlySchedulableTaskCount());
+ copy.setUseCompression(original.getUseCompression());
+ copy.setExists(original.getExists());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ final BatchSettingsDTO batchOrg = original.getBatchSettings();
+ if (batchOrg != null) {
+ final BatchSettingsDTO batchCopy = new BatchSettingsDTO();
+ batchCopy.setCount(batchOrg.getCount());
+ batchCopy.setSize(batchOrg.getSize());
+ batchCopy.setDuration(batchOrg.getDuration());
+ copy.setBatchSettings(batchCopy);
+ }
+ return copy;
+ }
+
+ public ProcessGroupDTO copy(final ProcessGroupDTO original, final boolean deep) {
+ final ProcessGroupDTO copy = new ProcessGroupDTO();
+ copy.setComments(original.getComments());
+ copy.setContents(copy(original.getContents(), deep));
+ copy.setPosition(original.getPosition());
+ copy.setId(original.getId());
+ copy.setInputPortCount(original.getInputPortCount());
+ copy.setInvalidCount(original.getInvalidCount());
+ copy.setName(original.getName());
+ copy.setVersionControlInformation(copy(original.getVersionControlInformation()));
+ copy.setOutputPortCount(original.getOutputPortCount());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ copy.setRunningCount(original.getRunningCount());
+ copy.setStoppedCount(original.getStoppedCount());
+ copy.setDisabledCount(original.getDisabledCount());
+ copy.setActiveRemotePortCount(original.getActiveRemotePortCount());
+ copy.setInactiveRemotePortCount(original.getInactiveRemotePortCount());
+
+ copy.setUpToDateCount(original.getUpToDateCount());
+ copy.setLocallyModifiedCount(original.getLocallyModifiedCount());
+ copy.setStaleCount(original.getStaleCount());
+ copy.setLocallyModifiedAndStaleCount(original.getLocallyModifiedAndStaleCount());
+ copy.setSyncFailureCount(original.getSyncFailureCount());
+
+ if (original.getVariables() != null) {
+ copy.setVariables(new HashMap<>(original.getVariables()));
+ }
+
+ return copy;
+ }
+
+ public VersionControlInformationDTO copy(final VersionControlInformationDTO original) {
+ if (original == null) {
+ return null;
+ }
+
+ final VersionControlInformationDTO copy = new VersionControlInformationDTO();
+ copy.setRegistryId(original.getRegistryId());
+ copy.setRegistryName(original.getRegistryName());
+ copy.setBucketId(original.getBucketId());
+ copy.setBucketName(original.getBucketName());
+ copy.setFlowId(original.getFlowId());
+ copy.setFlowName(original.getFlowName());
+ copy.setFlowDescription(original.getFlowDescription());
+ copy.setVersion(original.getVersion());
+ copy.setState(original.getState());
+ copy.setStateExplanation(original.getStateExplanation());
+ return copy;
+ }
+
+ public RemoteProcessGroupDTO copy(final RemoteProcessGroupDTO original) {
+ final RemoteProcessGroupContentsDTO originalContents = original.getContents();
+ final RemoteProcessGroupContentsDTO copyContents = new RemoteProcessGroupContentsDTO();
+
+ if (originalContents.getInputPorts() != null) {
+ final Set<RemoteProcessGroupPortDTO> inputPorts = new HashSet<>();
+ for (final RemoteProcessGroupPortDTO port : originalContents.getInputPorts()) {
+ inputPorts.add(copy(port));
+ }
+ copyContents.setInputPorts(inputPorts);
+ }
+
+ if (originalContents.getOutputPorts() != null) {
+ final Set<RemoteProcessGroupPortDTO> outputPorts = new HashSet<>();
+ for (final RemoteProcessGroupPortDTO port : originalContents.getOutputPorts()) {
+ outputPorts.add(copy(port));
+ }
+ copyContents.setOutputPorts(outputPorts);
+ }
+
+ final RemoteProcessGroupDTO copy = new RemoteProcessGroupDTO();
+ copy.setComments(original.getComments());
+ copy.setPosition(original.getPosition());
+ copy.setId(original.getId());
+ copy.setCommunicationsTimeout(original.getCommunicationsTimeout());
+ copy.setYieldDuration(original.getYieldDuration());
+ copy.setName(original.getName());
+ copy.setInputPortCount(original.getInputPortCount());
+ copy.setOutputPortCount(original.getOutputPortCount());
+ copy.setActiveRemoteInputPortCount(original.getActiveRemoteInputPortCount());
+ copy.setInactiveRemoteInputPortCount(original.getInactiveRemoteInputPortCount());
+ copy.setActiveRemoteOutputPortCount(original.getActiveRemoteOutputPortCount());
+ copy.setInactiveRemoteOutputPortCount(original.getInactiveRemoteOutputPortCount());
+ copy.setParentGroupId(original.getParentGroupId());
+ copy.setTargetUris(original.getTargetUris());
+ copy.setTransportProtocol(original.getTransportProtocol());
+ copy.setProxyHost(original.getProxyHost());
+ copy.setProxyPort(original.getProxyPort());
+ copy.setProxyUser(original.getProxyUser());
+ copy.setProxyPassword(original.getProxyPassword());
+ copy.setLocalNetworkInterface(original.getLocalNetworkInterface());
+ copy.setVersionedComponentId(original.getVersionedComponentId());
+
+ copy.setContents(copyContents);
+
+ return copy;
+ }
+
+ public ConnectableDTO createConnectableDto(final PortDTO port, final ConnectableType type) {
+ final ConnectableDTO connectable = new ConnectableDTO();
+ connectable.setGroupId(port.getParentGroupId());
+ connectable.setId(port.getId());
+ connectable.setName(port.getName());
+ connectable.setType(type.name());
+ connectable.setVersionedComponentId(port.getVersionedComponentId());
+ return connectable;
+ }
+
+ public ConnectableDTO createConnectableDto(final ProcessorDTO processor) {
+ final ConnectableDTO connectable = new ConnectableDTO();
+ connectable.setGroupId(processor.getParentGroupId());
+ connectable.setId(processor.getId());
+ connectable.setName(processor.getName());
+ connectable.setType(ConnectableType.PROCESSOR.name());
+ connectable.setVersionedComponentId(processor.getVersionedComponentId());
+ return connectable;
+ }
+
+ public ConnectableDTO createConnectableDto(final FunnelDTO funnel) {
+ final ConnectableDTO connectable = new ConnectableDTO();
+ connectable.setGroupId(funnel.getParentGroupId());
+ connectable.setId(funnel.getId());
+ connectable.setType(ConnectableType.FUNNEL.name());
+ connectable.setVersionedComponentId(funnel.getVersionedComponentId());
+ return connectable;
+ }
+
+ public ConnectableDTO createConnectableDto(final RemoteProcessGroupPortDTO remoteGroupPort, final ConnectableType type) {
+ final ConnectableDTO connectable = new ConnectableDTO();
+ connectable.setGroupId(remoteGroupPort.getGroupId());
+ connectable.setId(remoteGroupPort.getId());
+ connectable.setName(remoteGroupPort.getName());
+ connectable.setType(type.name());
+ connectable.setVersionedComponentId(connectable.getVersionedComponentId());
+ return connectable;
+ }
+
+ /**
+ *
+ * @param original orig
+ * @param deep if <code>true</code>, all Connections, ProcessGroups, Ports, Processors, etc. will be copied. If <code>false</code>, the copy will have links to the same objects referenced by
+ * <code>original</code>.
+ *
+ * @return dto
+ */
+ private FlowSnippetDTO copy(final FlowSnippetDTO original, final boolean deep) {
+ final FlowSnippetDTO copy = new FlowSnippetDTO();
+
+ final Set<ConnectionDTO> connections = new LinkedHashSet<>();
+ final Set<ProcessGroupDTO> groups = new LinkedHashSet<>();
+ final Set<PortDTO> inputPorts = new LinkedHashSet<>();
+ final Set<PortDTO> outputPorts = new LinkedHashSet<>();
+ final Set<LabelDTO> labels = new LinkedHashSet<>();
+ final Set<ProcessorDTO> processors = new LinkedHashSet<>();
+ final Set<RemoteProcessGroupDTO> remoteProcessGroups = new LinkedHashSet<>();
+ final Set<FunnelDTO> funnels = new LinkedHashSet<>();
+ final Set<ControllerServiceDTO> controllerServices = new LinkedHashSet<>();
+
+ if (deep) {
+ for (final ProcessGroupDTO group : original.getProcessGroups()) {
+ groups.add(copy(group, deep));
+ }
+
+ for (final PortDTO port : original.getInputPorts()) {
+ inputPorts.add(copy(port));
+ }
+
+ for (final PortDTO port : original.getOutputPorts()) {
+ outputPorts.add(copy(port));
+ }
+
+ for (final LabelDTO label : original.getLabels()) {
+ labels.add(copy(label));
+ }
+
+ for (final ProcessorDTO processor : original.getProcessors()) {
+ processors.add(copy(processor));
+ }
+
+ for (final RemoteProcessGroupDTO remoteGroup : original.getRemoteProcessGroups()) {
+ remoteProcessGroups.add(copy(remoteGroup));
+ }
+
+ for (final FunnelDTO funnel : original.getFunnels()) {
+ funnels.add(copy(funnel));
+ }
+
+ for (final ConnectionDTO connection : original.getConnections()) {
+ connections.add(copy(connection));
+ }
+
+ for (final ControllerServiceDTO controllerService : original.getControllerServices()) {
+ controllerServices.add(copy(controllerService));
+ }
+ } else {
+ if (original.getConnections() != null) {
+ connections.addAll(copy(original.getConnections()));
+ }
+ if (original.getProcessGroups() != null) {
+ groups.addAll(copy(original.getProcessGroups()));
+ }
+ if (original.getInputPorts() != null) {
+ inputPorts.addAll(copy(original.getInputPorts()));
+ }
+ if (original.getOutputPorts() != null) {
+ outputPorts.addAll(copy(original.getOutputPorts()));
+ }
+ if (original.getLabels() != null) {
+ labels.addAll(copy(original.getLabels()));
+ }
+ if (original.getProcessors() != null) {
+ processors.addAll(copy(original.getProcessors()));
+ }
+ if (original.getRemoteProcessGroups() != null) {
+ remoteProcessGroups.addAll(copy(original.getRemoteProcessGroups()));
+ }
+ if (original.getFunnels() != null) {
+ funnels.addAll(copy(original.getFunnels()));
+ }
+ if (original.getControllerServices() != null) {
+ controllerServices.addAll(copy(original.getControllerServices()));
+ }
+ }
+
+ copy.setConnections(connections);
+ copy.setProcessGroups(groups);
+ copy.setInputPorts(inputPorts);
+ copy.setLabels(labels);
+ copy.setOutputPorts(outputPorts);
+ copy.setProcessors(processors);
+ copy.setRemoteProcessGroups(remoteProcessGroups);
+ copy.setFunnels(funnels);
+ copy.setControllerServices(controllerServices);
+
+ return copy;
+ }
+
+ /**
+ * Factory method for creating a new RevisionDTO based on this controller.
+ *
+ * @param lastMod mod
+ * @return dto
+ */
+ public RevisionDTO createRevisionDTO(final FlowModification lastMod) {
+ final Revision revision = lastMod.getRevision();
+
+ // create the dto
+ final RevisionDTO revisionDTO = new RevisionDTO();
+ revisionDTO.setVersion(revision.getVersion());
+ revisionDTO.setClientId(revision.getClientId());
+ revisionDTO.setLastModifier(lastMod.getLastModifier());
+
+ return revisionDTO;
+ }
+
+ public RevisionDTO createRevisionDTO(final Revision revision) {
+ final RevisionDTO dto = new RevisionDTO();
+ dto.setVersion(revision.getVersion());
+ dto.setClientId(revision.getClientId());
+ return dto;
+ }
+
+ public NodeDTO createNodeDTO(final NodeIdentifier nodeId, final NodeConnectionStatus status, final NodeHeartbeat nodeHeartbeat, final List<NodeEvent> events, final Set<String> roles) {
+ final NodeDTO nodeDto = new NodeDTO();
+
+ // populate node dto
+ nodeDto.setNodeId(nodeId.getId());
+ nodeDto.setAddress(nodeId.getApiAddress());
+ nodeDto.setApiPort(nodeId.getApiPort());
+ nodeDto.setStatus(status.getState().name());
+ nodeDto.setRoles(roles);
+ if (status.getConnectionRequestTime() != null) {
+ final Date connectionRequested = new Date(status.getConnectionRequestTime());
+ nodeDto.setConnectionRequested(connectionRequested);
+ }
+
+ // only connected nodes have heartbeats
+ if (nodeHeartbeat != null) {
+ final Date heartbeat = new Date(nodeHeartbeat.getTimestamp());
+ nodeDto.setHeartbeat(heartbeat);
+ nodeDto.setNodeStartTime(new Date(nodeHeartbeat.getSystemStartTime()));
+ nodeDto.setActiveThreadCount(nodeHeartbeat.getActiveThreadCount());
+ nodeDto.setQueued(FormatUtils.formatCount(nodeHeartbeat.getFlowFileCount()) + " / " + FormatUtils.formatDataSize(nodeHeartbeat.getFlowFileBytes()));
+ }
+
+ // populate node events
+ final List<NodeEvent> nodeEvents = new ArrayList<>(events);
+ Collections.sort(nodeEvents, new Comparator<NodeEvent>() {
+ @Override
+ public int compare(final NodeEvent event1, final NodeEvent event2) {
+ return new Date(event2.getTimestamp()).compareTo(new Date(event1.getTimestamp()));
+ }
+ });
+
+ // create the node event dtos
+ final List<NodeEventDTO> nodeEventDtos = new ArrayList<>();
+ for (final NodeEvent event : nodeEvents) {
+ // create node event dto
+ final NodeEventDTO nodeEventDto = new NodeEventDTO();
+ nodeEventDtos.add(nodeEventDto);
+
+ // populate node event dto
+ nodeEventDto.setMessage(event.getMessage());
+ nodeEventDto.setCategory(event.getSeverity().name());
+ nodeEventDto.setTimestamp(new Date(event.getTimestamp()));
+ }
+ nodeDto.setEvents(nodeEventDtos);
+
+ return nodeDto;
+ }
+
+ public RegistryDTO createRegistryDto(FlowRegistry registry) {
+ final RegistryDTO dto = new RegistryDTO();
+ dto.setDescription(registry.getDescription());
+ dto.setId(registry.getIdentifier());
+ dto.setName(registry.getName());
+ dto.setUri(registry.getURL());
+ return dto;
+ }
+
+
+ /* setters */
+ public void setControllerServiceProvider(final ControllerServiceProvider controllerServiceProvider) {
+ this.controllerServiceProvider = controllerServiceProvider;
+ }
+
+ public void setAuthorizer(final Authorizer authorizer) {
+ this.authorizer = authorizer;
+ }
+
+ public void setEntityFactory(final EntityFactory entityFactory) {
+ this.entityFactory = entityFactory;
+ }
+
+ public void setBulletinRepository(BulletinRepository bulletinRepository) {
+ this.bulletinRepository = bulletinRepository;
+ }
+
+ public void setExtensionManager(ExtensionManager extensionManager) {
+ this.extensionManager = extensionManager;
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
new file mode 100644
index 0000000..2dd91ad
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.util.TimeAdapter;
+
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import java.util.Date;
+
+/**
+ * Details for the controller configuration.
+ */
+@XmlType(name = "flowConfiguration")
+public class FlowConfigurationDTO {
+
+ private Boolean supportsManagedAuthorizer;
+ private Boolean supportsConfigurableAuthorizer;
+ private Boolean supportsConfigurableUsersAndGroups;
+ private Long autoRefreshIntervalSeconds;
+
+ private Date currentTime;
+ private Integer timeOffset;
+
+ private Long defaultBackPressureObjectThreshold;
+ private String defaultBackPressureDataSizeThreshold;
+
+ private String dcaeDistributorApiHostname;
+
+ /**
+ * @author Renu
+ * @return getter and setter for dcae distributor api hostname. This value is read only
+ */
+ @ApiModelProperty(
+ value = "Whether it picks up configurable host address.",
+ readOnly = true
+ )
+ public String getDcaeDistributorApiHostname() {
+ return dcaeDistributorApiHostname;
+ }
+
+ public void setDcaeDistributorApiHostname(String dcaeDistributorApiHostname) {
+ this.dcaeDistributorApiHostname = dcaeDistributorApiHostname;
+ }
+
+ /**
+ * @return interval in seconds between the automatic NiFi refresh requests. This value is read only
+ */
+ @ApiModelProperty(
+ value = "The interval in seconds between the automatic NiFi refresh requests.",
+ readOnly = true
+ )
+ public Long getAutoRefreshIntervalSeconds() {
+ return autoRefreshIntervalSeconds;
+ }
+
+ public void setAutoRefreshIntervalSeconds(Long autoRefreshIntervalSeconds) {
+ this.autoRefreshIntervalSeconds = autoRefreshIntervalSeconds;
+ }
+
+ /**
+ * @return whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups,
+ * and policies in the UI. This value is read only
+ */
+ @ApiModelProperty(
+ value = "Whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups, and policies in the UI.",
+ readOnly = true
+ )
+ public Boolean getSupportsManagedAuthorizer() {
+ return supportsManagedAuthorizer;
+ }
+
+ public void setSupportsManagedAuthorizer(Boolean supportsManagedAuthorizer) {
+ this.supportsManagedAuthorizer = supportsManagedAuthorizer;
+ }
+
+ /**
+ * @return whether this NiFi supports configurable users and groups. This value is read only
+ */
+ @ApiModelProperty(
+ value = "Whether this NiFi supports configurable users and groups.",
+ readOnly = true
+ )
+ public Boolean getSupportsConfigurableUsersAndGroups() {
+ return supportsConfigurableUsersAndGroups;
+ }
+
+ public void setSupportsConfigurableUsersAndGroups(Boolean supportsConfigurableUsersAndGroups) {
+ this.supportsConfigurableUsersAndGroups = supportsConfigurableUsersAndGroups;
+ }
+
+ /**
+ * @return whether this NiFi supports a configurable authorizer. This value is read only
+ */
+ @ApiModelProperty(
+ value = "Whether this NiFi supports a configurable authorizer.",
+ readOnly = true
+ )
+ public Boolean getSupportsConfigurableAuthorizer() {
+ return supportsConfigurableAuthorizer;
+ }
+
+ public void setSupportsConfigurableAuthorizer(Boolean supportsConfigurableAuthorizer) {
+ this.supportsConfigurableAuthorizer = supportsConfigurableAuthorizer;
+ }
+
+ /**
+ * @return current time on the server
+ */
+ @XmlJavaTypeAdapter(TimeAdapter.class)
+ @ApiModelProperty(
+ value = "The current time on the system.",
+ dataType = "string"
+ )
+ public Date getCurrentTime() {
+ return currentTime;
+ }
+
+ public void setCurrentTime(Date currentTime) {
+ this.currentTime = currentTime;
+ }
+
+ /**
+ * @return time offset of the server
+ */
+ @ApiModelProperty(
+ value = "The time offset of the system."
+ )
+ public Integer getTimeOffset() {
+ return timeOffset;
+ }
+
+ public void setTimeOffset(Integer timeOffset) {
+ this.timeOffset = timeOffset;
+ }
+
+ /**
+ * @return the default back pressure object threshold
+ */
+ @ApiModelProperty(
+ value = "The default back pressure object threshold."
+ )
+ public Long getDefaultBackPressureObjectThreshold() {
+ return defaultBackPressureObjectThreshold;
+ }
+
+ public void setDefaultBackPressureObjectThreshold(Long backPressureObjectThreshold) {
+ this.defaultBackPressureObjectThreshold = backPressureObjectThreshold;
+ }
+
+ /**
+ * @return the default back pressure data size threshold
+ */
+ @ApiModelProperty(
+ value = "The default back pressure data size threshold."
+ )
+ public String getDefaultBackPressureDataSizeThreshold() {
+ return defaultBackPressureDataSizeThreshold;
+ }
+
+ public void setDefaultBackPressureDataSizeThreshold(String backPressureDataSizeThreshold) {
+ this.defaultBackPressureDataSizeThreshold = backPressureDataSizeThreshold;
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
new file mode 100644
index 0000000..1343400
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java
@@ -0,0 +1,700 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.web.dao.impl;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.DataAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.ConnectableType;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.controller.queue.LoadBalanceCompression;
+import org.apache.nifi.controller.queue.LoadBalanceStrategy;
+import org.apache.nifi.connectable.Position;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.exception.ValidationException;
+import org.apache.nifi.controller.queue.DropFlowFileStatus;
+import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.ListFlowFileStatus;
+import org.apache.nifi.controller.repository.ContentNotFoundException;
+import org.apache.nifi.controller.repository.FlowFileRecord;
+import org.apache.nifi.flowfile.FlowFilePrioritizer;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.groups.RemoteProcessGroup;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.remote.RemoteGroupPort;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.web.DownloadableContent;
+import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.ConnectableDTO;
+import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.PositionDTO;
+import org.apache.nifi.web.dao.ConnectionDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.WebApplicationException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+
+public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO {
+
+ private static final Logger logger = LoggerFactory.getLogger(StandardConnectionDAO.class);
+
+ private FlowController flowController;
+ private Authorizer authorizer;
+
+ private Connection locateConnection(final String connectionId) {
+ final ProcessGroup rootGroup = flowController.getFlowManager().getRootGroup();
+ final Connection connection = rootGroup.findConnection(connectionId);
+
+ if (connection == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find connection with id '%s'.", connectionId));
+ } else {
+ return connection;
+ }
+ }
+
+ @Override
+ public boolean hasConnection(String id) {
+ final ProcessGroup rootGroup = flowController.getFlowManager().getRootGroup();
+ return rootGroup.findConnection(id) != null;
+ }
+
+ @Override
+ public Connection getConnection(final String id) {
+ return locateConnection(id);
+ }
+
+ @Override
+ public Set<Connection> getConnections(final String groupId) {
+ final ProcessGroup group = locateProcessGroup(flowController, groupId);
+ return group.getConnections();
+ }
+
+ @Override
+ public DropFlowFileStatus getFlowFileDropRequest(String connectionId, String dropRequestId) {
+ final Connection connection = locateConnection(connectionId);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ final DropFlowFileStatus dropRequest = queue.getDropFlowFileStatus(dropRequestId);
+ if (dropRequest == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find drop request with id '%s'.", dropRequestId));
+ }
+
+ return dropRequest;
+ }
+
+ @Override
+ public ListFlowFileStatus getFlowFileListingRequest(String connectionId, String listingRequestId) {
+ final Connection connection = locateConnection(connectionId);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ final ListFlowFileStatus listRequest = queue.getListFlowFileStatus(listingRequestId);
+ if (listRequest == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find listing request with id '%s'.", listingRequestId));
+ }
+
+ return listRequest;
+ }
+
+ @Override
+ public FlowFileRecord getFlowFile(String id, String flowFileUuid) {
+ try {
+ final Connection connection = locateConnection(id);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+ final FlowFileRecord flowFile = queue.getFlowFile(flowFileUuid);
+
+ if (flowFile == null) {
+ throw new ResourceNotFoundException(String.format("The FlowFile with UUID %s is no longer in the active queue.", flowFileUuid));
+ }
+
+ // get the attributes and ensure appropriate access
+ final Map<String, String> attributes = flowFile.getAttributes();
+ final Authorizable dataAuthorizable = new DataAuthorizable(connection.getSourceAuthorizable());
+ dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser(), attributes);
+
+ return flowFile;
+ } catch (final IOException ioe) {
+ logger.error(String.format("Unable to get the flowfile (%s) at this time.", flowFileUuid), ioe);
+ throw new IllegalStateException("Unable to get the FlowFile at this time.");
+ }
+ }
+
+ /**
+ * Configures the specified connection using the specified dto.
+ */
+ private void configureConnection(Connection connection, ConnectionDTO connectionDTO) {
+ // validate flow file comparators/prioritizers
+ List<FlowFilePrioritizer> newPrioritizers = null;
+ final List<String> prioritizers = connectionDTO.getPrioritizers();
+ if (isNotNull(prioritizers)) {
+ final List<String> newPrioritizersClasses = new ArrayList<>(prioritizers);
+ newPrioritizers = new ArrayList<>();
+ for (final String className : newPrioritizersClasses) {
+ try {
+ newPrioritizers.add(flowController.getFlowManager().createPrioritizer(className));
+ } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+ throw new IllegalArgumentException("Unable to set prioritizer " + className + ": " + e);
+ }
+ }
+ }
+
+ // update connection queue
+ if (isNotNull(connectionDTO.getFlowFileExpiration())) {
+ connection.getFlowFileQueue().setFlowFileExpiration(connectionDTO.getFlowFileExpiration());
+ }
+ if (isNotNull(connectionDTO.getBackPressureObjectThreshold())) {
+ connection.getFlowFileQueue().setBackPressureObjectThreshold(connectionDTO.getBackPressureObjectThreshold());
+ }
+ if (isNotNull(connectionDTO.getBackPressureDataSizeThreshold())) {
+ connection.getFlowFileQueue().setBackPressureDataSizeThreshold(connectionDTO.getBackPressureDataSizeThreshold());
+ }
+ if (isNotNull(newPrioritizers)) {
+ connection.getFlowFileQueue().setPriorities(newPrioritizers);
+ }
+
+ final String loadBalanceStrategyName = connectionDTO.getLoadBalanceStrategy();
+ final String loadBalancePartitionAttribute = connectionDTO.getLoadBalancePartitionAttribute();
+ if (isNotNull(loadBalanceStrategyName)) {
+ final LoadBalanceStrategy loadBalanceStrategy = LoadBalanceStrategy.valueOf(loadBalanceStrategyName);
+ connection.getFlowFileQueue().setLoadBalanceStrategy(loadBalanceStrategy, loadBalancePartitionAttribute);
+ }
+
+ final String loadBalanceCompressionName = connectionDTO.getLoadBalanceCompression();
+ if (isNotNull(loadBalanceCompressionName)) {
+ connection.getFlowFileQueue().setLoadBalanceCompression(LoadBalanceCompression.valueOf(loadBalanceCompressionName));
+ }
+
+ // update the connection state
+ if (isNotNull(connectionDTO.getBends())) {
+ final List<Position> bendPoints = new ArrayList<>();
+ for (final PositionDTO bend : connectionDTO.getBends()) {
+ if (bend != null) {
+ bendPoints.add(new Position(bend.getX(), bend.getY()));
+ }
+ }
+ connection.setBendPoints(bendPoints);
+ }
+ if (isNotNull(connectionDTO.getName())) {
+ connection.setName(connectionDTO.getName());
+ }
+ if (isNotNull(connectionDTO.getLabelIndex())) {
+ connection.setLabelIndex(connectionDTO.getLabelIndex());
+ }
+ if (isNotNull(connectionDTO.getzIndex())) {
+ connection.setZIndex(connectionDTO.getzIndex());
+ }
+ }
+
+ /**
+ * Validates the proposed processor configuration.
+ */
+ private List<String> validateProposedConfiguration(final String groupId, final ConnectionDTO connectionDTO) {
+ List<String> validationErrors = new ArrayList<>();
+
+ if (isNotNull(connectionDTO.getBackPressureObjectThreshold()) && connectionDTO.getBackPressureObjectThreshold() < 0) {
+ validationErrors.add("Max queue size must be a non-negative integer");
+ }
+ if (isNotNull(connectionDTO.getFlowFileExpiration())) {
+ Matcher expirationMatcher = FormatUtils.TIME_DURATION_PATTERN.matcher(connectionDTO.getFlowFileExpiration());
+ if (!expirationMatcher.matches()) {
+ validationErrors.add("Flow file expiration is not a valid time duration (ie 30 sec, 5 min)");
+ }
+ }
+ if (isNotNull(connectionDTO.getLabelIndex())) {
+ if (connectionDTO.getLabelIndex() < 0) {
+ validationErrors.add("The label index must be positive.");
+ }
+ }
+
+ // validation is required when connecting to a remote process group since each node in a
+ // cluster may or may not be authorized
+ final ConnectableDTO proposedDestination = connectionDTO.getDestination();
+ if (proposedDestination != null && ConnectableType.REMOTE_INPUT_PORT.name().equals(proposedDestination.getType())) {
+ // the group id must be specified
+ if (proposedDestination.getGroupId() == null) {
+ validationErrors.add("When the destination is a remote input port its group id is required.");
+ return validationErrors;
+ }
+
+ // attempt to location the proprosed destination
+ final ProcessGroup destinationParentGroup = locateProcessGroup(flowController, groupId);
+ final RemoteProcessGroup remoteProcessGroup = destinationParentGroup.getRemoteProcessGroup(proposedDestination.getGroupId());
+ if (remoteProcessGroup == null) {
+ validationErrors.add("Unable to find the specified remote process group.");
+ return validationErrors;
+ }
+
+ // ensure the new destination was found
+ final RemoteGroupPort remoteInputPort = remoteProcessGroup.getInputPort(proposedDestination.getId());
+ if (remoteInputPort == null) {
+ validationErrors.add("Unable to find the specified destination.");
+ return validationErrors;
+ }
+ }
+
+ return validationErrors;
+ }
+
+ @Override
+ public Connection createConnection(final String groupId, final ConnectionDTO connectionDTO) {
+ final ProcessGroup group = locateProcessGroup(flowController, groupId);
+
+ if (isNotNull(connectionDTO.getParentGroupId()) && !flowController.getFlowManager().areGroupsSame(connectionDTO.getParentGroupId(), groupId)) {
+ throw new IllegalStateException("Cannot specify a different Parent Group ID than the Group to which the Connection is being added");
+ }
+
+ // get the source and destination connectables
+ final ConnectableDTO sourceConnectableDTO = connectionDTO.getSource();
+ final ConnectableDTO destinationConnectableDTO = connectionDTO.getDestination();
+
+ // ensure both are specified
+ if (sourceConnectableDTO == null || destinationConnectableDTO == null) {
+ throw new IllegalArgumentException("Both source and destinations must be specified.");
+ }
+
+ // if the source/destination connectable's group id has not been set, its inferred to be the current group
+ if (sourceConnectableDTO.getGroupId() == null) {
+ sourceConnectableDTO.setGroupId(groupId);
+ }
+ if (destinationConnectableDTO.getGroupId() == null) {
+ destinationConnectableDTO.setGroupId(groupId);
+ }
+
+ // validate the proposed configuration
+ final List<String> validationErrors = validateProposedConfiguration(groupId, connectionDTO);
+
+ // ensure there was no validation errors
+ if (!validationErrors.isEmpty()) {
+ throw new ValidationException(validationErrors);
+ }
+
+ // find the source
+ final Connectable source;
+ if (ConnectableType.REMOTE_OUTPUT_PORT.name().equals(sourceConnectableDTO.getType())) {
+ final ProcessGroup sourceParentGroup = locateProcessGroup(flowController, groupId);
+ final RemoteProcessGroup remoteProcessGroup = sourceParentGroup.getRemoteProcessGroup(sourceConnectableDTO.getGroupId());
+ source = remoteProcessGroup.getOutputPort(sourceConnectableDTO.getId());
+ } else {
+ final ProcessGroup sourceGroup = locateProcessGroup(flowController, sourceConnectableDTO.getGroupId());
+ source = sourceGroup.getConnectable(sourceConnectableDTO.getId());
+ }
+
+ // find the destination
+ final Connectable destination;
+ if (ConnectableType.REMOTE_INPUT_PORT.name().equals(destinationConnectableDTO.getType())) {
+ final ProcessGroup destinationParentGroup = locateProcessGroup(flowController, groupId);
+ final RemoteProcessGroup remoteProcessGroup = destinationParentGroup.getRemoteProcessGroup(destinationConnectableDTO.getGroupId());
+ destination = remoteProcessGroup.getInputPort(destinationConnectableDTO.getId());
+ } else {
+ final ProcessGroup destinationGroup = locateProcessGroup(flowController, destinationConnectableDTO.getGroupId());
+ destination = destinationGroup.getConnectable(destinationConnectableDTO.getId());
+ }
+
+ // determine the relationships
+ final Set<String> relationships = new HashSet<>();
+ if (isNotNull(connectionDTO.getSelectedRelationships())) {
+ relationships.addAll(connectionDTO.getSelectedRelationships());
+ }
+
+ // create the connection
+ final Connection connection = flowController.createConnection(connectionDTO.getId(), connectionDTO.getName(), source, destination, relationships);
+
+ // configure the connection
+ configureConnection(connection, connectionDTO);
+
+ // add the connection to the group
+ group.addConnection(connection);
+ return connection;
+ }
+
+ @Override
+ public DropFlowFileStatus createFlowFileDropRequest(String id, String dropRequestId) {
+ final Connection connection = locateConnection(id);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ if (user == null) {
+ throw new WebApplicationException(new Throwable("Unable to access details for current user."));
+ }
+
+ return queue.dropFlowFiles(dropRequestId, user.getIdentity());
+ }
+
+ @Override
+ public ListFlowFileStatus createFlowFileListingRequest(String id, String listingRequestId) {
+ final Connection connection = locateConnection(id);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ // ensure we can list
+ verifyList(queue);
+
+ return queue.listFlowFiles(listingRequestId, 100);
+ }
+
+ @Override
+ public void verifyCreate(String groupId, ConnectionDTO connectionDTO) {
+ // validate the incoming request
+ final List<String> validationErrors = validateProposedConfiguration(groupId, connectionDTO);
+
+ // ensure there was no validation errors
+ if (!validationErrors.isEmpty()) {
+ throw new ValidationException(validationErrors);
+ }
+
+ // Ensure that both the source and the destination for the connection exist.
+ // In the case that the source or destination is a port in a Remote Process Group,
+ // this is necessary because the ports can change in the background. It may still be
+ // possible for a port to disappear between the 'verify' stage and the creation stage,
+ // but this prevents the case where some nodes already know about the port while other
+ // nodes in the cluster do not. This is a more common case, as users may try to connect
+ // to the port as soon as the port is created.
+ final ConnectableDTO sourceDto = connectionDTO.getSource();
+ if (sourceDto == null || sourceDto.getId() == null) {
+ throw new IllegalArgumentException("Cannot create connection without specifying source");
+ }
+
+ final ConnectableDTO destinationDto = connectionDTO.getDestination();
+ if (destinationDto == null || destinationDto.getId() == null) {
+ throw new IllegalArgumentException("Cannot create connection without specifying destination");
+ }
+
+ if (ConnectableType.REMOTE_OUTPUT_PORT.name().equals(sourceDto.getType())) {
+ final ProcessGroup sourceParentGroup = locateProcessGroup(flowController, groupId);
+
+ final RemoteProcessGroup remoteProcessGroup = sourceParentGroup.getRemoteProcessGroup(sourceDto.getGroupId());
+ if (remoteProcessGroup == null) {
+ throw new IllegalArgumentException("Unable to find the specified remote process group.");
+ }
+
+ final RemoteGroupPort sourceConnectable = remoteProcessGroup.getOutputPort(sourceDto.getId());
+ if (sourceConnectable == null) {
+ throw new IllegalArgumentException("The specified source for the connection does not exist");
+ } else if (!sourceConnectable.getTargetExists()) {
+ throw new IllegalArgumentException("The specified remote output port does not exist.");
+ }
+ } else {
+ final ProcessGroup sourceGroup = locateProcessGroup(flowController, sourceDto.getGroupId());
+ final Connectable sourceConnectable = sourceGroup.getConnectable(sourceDto.getId());
+ if (sourceConnectable == null) {
+ throw new IllegalArgumentException("The specified source for the connection does not exist");
+ }
+ }
+
+ if (ConnectableType.REMOTE_INPUT_PORT.name().equals(destinationDto.getType())) {
+ final ProcessGroup destinationParentGroup = locateProcessGroup(flowController, groupId);
+
+ final RemoteProcessGroup remoteProcessGroup = destinationParentGroup.getRemoteProcessGroup(destinationDto.getGroupId());
+ if (remoteProcessGroup == null) {
+ throw new IllegalArgumentException("Unable to find the specified remote process group.");
+ }
+
+ final RemoteGroupPort destinationConnectable = remoteProcessGroup.getInputPort(destinationDto.getId());
+ if (destinationConnectable == null) {
+ throw new IllegalArgumentException("The specified destination for the connection does not exist");
+ } else if (!destinationConnectable.getTargetExists()) {
+ throw new IllegalArgumentException("The specified remote input port does not exist.");
+ }
+ } else {
+ final ProcessGroup destinationGroup = locateProcessGroup(flowController, destinationDto.getGroupId());
+ final Connectable destinationConnectable = destinationGroup.getConnectable(destinationDto.getId());
+ if (destinationConnectable == null) {
+ throw new IllegalArgumentException("The specified destination for the connection does not exist");
+ }
+ }
+ }
+
+ private void verifyList(final FlowFileQueue queue) {
+ queue.verifyCanList();
+ }
+
+ @Override
+ public void verifyList(String id) {
+ final Connection connection = locateConnection(id);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+ verifyList(queue);
+ }
+
+ @Override
+ public void verifyUpdate(ConnectionDTO connectionDTO) {
+ verifyUpdate(locateConnection(connectionDTO.getId()), connectionDTO);
+ }
+
+ private void verifyUpdate(final Connection connection, final ConnectionDTO connectionDTO) {
+ // determine what the request is attempting
+ if (isAnyNotNull(connectionDTO.getBackPressureDataSizeThreshold(),
+ connectionDTO.getBackPressureObjectThreshold(),
+ connectionDTO.getDestination(),
+ connectionDTO.getFlowFileExpiration(),
+ connectionDTO.getName(),
+ connectionDTO.getPosition(),
+ connectionDTO.getPrioritizers(),
+ connectionDTO.getSelectedRelationships())) {
+
+ // validate the incoming request
+ final List<String> validationErrors = validateProposedConfiguration(connection.getProcessGroup().getIdentifier(), connectionDTO);
+
+ // ensure there was no validation errors
+ if (!validationErrors.isEmpty()) {
+ throw new ValidationException(validationErrors);
+ }
+
+ // If destination is changing, ensure that current destination is not running. This check is done here, rather than
+ // in the Connection object itself because the Connection object itself does not know which updates are to occur and
+ // we don't want to prevent updating things like the connection name or backpressure just because the destination is running
+ final Connectable destination = connection.getDestination();
+ if (destination != null && destination.isRunning() && destination.getConnectableType() != ConnectableType.FUNNEL && destination.getConnectableType() != ConnectableType.INPUT_PORT) {
+ throw new ValidationException(Collections.singletonList("Cannot change the destination of connection because the current destination is running"));
+ }
+
+ // verify that this connection supports modification
+ connection.verifyCanUpdate();
+ }
+ }
+
+ @Override
+ public Connection updateConnection(final ConnectionDTO connectionDTO) {
+ final Connection connection = locateConnection(connectionDTO.getId());
+ final ProcessGroup group = connection.getProcessGroup();
+
+ // ensure we can update
+ verifyUpdate(connection, connectionDTO);
+
+ final Collection<Relationship> newProcessorRelationships = new ArrayList<>();
+ Connectable newDestination = null;
+
+ // ensure that the source ID is correct, if specified.
+ final Connectable existingSource = connection.getSource();
+ if (isNotNull(connectionDTO.getSource()) && !existingSource.getIdentifier().equals(connectionDTO.getSource().getId())) {
+ throw new IllegalStateException("Connection with ID " + connectionDTO.getId() + " has conflicting Source ID");
+ }
+
+ // determine if the destination changed
+ final ConnectableDTO proposedDestination = connectionDTO.getDestination();
+ if (proposedDestination != null) {
+ final Connectable currentDestination = connection.getDestination();
+
+ // handle remote input port differently
+ if (ConnectableType.REMOTE_INPUT_PORT.name().equals(proposedDestination.getType())) {
+ // the group id must be specified
+ if (proposedDestination.getGroupId() == null) {
+ throw new IllegalArgumentException("When the destination is a remote input port its group id is required.");
+ }
+
+ // if the current destination is a remote input port
+ boolean isDifferentRemoteProcessGroup = false;
+ if (currentDestination.getConnectableType() == ConnectableType.REMOTE_INPUT_PORT) {
+ RemoteGroupPort remotePort = (RemoteGroupPort) currentDestination;
+ if (!proposedDestination.getGroupId().equals(remotePort.getRemoteProcessGroup().getIdentifier())) {
+ isDifferentRemoteProcessGroup = true;
+ }
+ }
+
+ // if the destination is changing or the previous destination was a different remote process group
+ if (!proposedDestination.getId().equals(currentDestination.getIdentifier()) || isDifferentRemoteProcessGroup) {
+ final ProcessGroup destinationParentGroup = locateProcessGroup(flowController, group.getIdentifier());
+ final RemoteProcessGroup remoteProcessGroup = destinationParentGroup.getRemoteProcessGroup(proposedDestination.getGroupId());
+
+ // ensure the remote process group was found
+ if (remoteProcessGroup == null) {
+ throw new IllegalArgumentException("Unable to find the specified remote process group.");
+ }
+
+ final RemoteGroupPort remoteInputPort = remoteProcessGroup.getInputPort(proposedDestination.getId());
+
+ // ensure the new destination was found
+ if (remoteInputPort == null) {
+ throw new IllegalArgumentException("Unable to find the specified destination.");
+ }
+
+ // ensure the remote port actually exists
+ if (!remoteInputPort.getTargetExists()) {
+ throw new IllegalArgumentException("The specified remote input port does not exist.");
+ } else {
+ newDestination = remoteInputPort;
+ }
+ }
+ } else {
+ // if there is a different destination id
+ if (!proposedDestination.getId().equals(currentDestination.getIdentifier())) {
+ // if the destination connectable's group id has not been set, its inferred to be the current group
+ if (proposedDestination.getGroupId() == null) {
+ proposedDestination.setGroupId(group.getIdentifier());
+ }
+
+ final ProcessGroup destinationGroup = locateProcessGroup(flowController, proposedDestination.getGroupId());
+ newDestination = destinationGroup.getConnectable(proposedDestination.getId());
+
+ // ensure the new destination was found
+ if (newDestination == null) {
+ throw new IllegalArgumentException("Unable to find the specified destination.");
+ }
+ }
+ }
+ }
+
+ // determine any new relationships
+ final Set<String> relationships = connectionDTO.getSelectedRelationships();
+ if (isNotNull(relationships)) {
+ if (relationships.isEmpty()) {
+ throw new IllegalArgumentException("Cannot remove all relationships from Connection with ID " + connection.getIdentifier() + " -- remove the Connection instead");
+ }
+ if (existingSource == null) {
+ throw new IllegalArgumentException("Cannot specify new relationships without including the source.");
+ }
+
+ final Connectable destination = newDestination == null ? connection.getDestination() : newDestination;
+
+ for (final String relationship : relationships) {
+ int prevSize = newProcessorRelationships.size();
+
+ final Relationship processorRelationshipSource = existingSource.getRelationship(relationship);
+
+ if (processorRelationshipSource != null) {
+ newProcessorRelationships.add(processorRelationshipSource);
+ }
+
+ final Relationship processorRelationshipDest = destination.getRelationship(relationship);
+
+ if (processorRelationshipDest != null) {
+ newProcessorRelationships.add(processorRelationshipDest);
+ }
+
+ if (newProcessorRelationships.size() == prevSize) {
+ throw new IllegalArgumentException("Unable to locate " + relationship + " relationship.");
+ }
+ }
+ }
+
+ // configure the connection
+ configureConnection(connection, connectionDTO);
+ group.onComponentModified();
+
+ // update the relationships if necessary
+ if (!newProcessorRelationships.isEmpty()) {
+ connection.setRelationships(newProcessorRelationships);
+ }
+
+ // update the destination if necessary
+ if (isNotNull(newDestination)) {
+ connection.setDestination(newDestination);
+ }
+
+ return connection;
+ }
+
+ @Override
+ public void verifyDelete(String id) {
+ final Connection connection = locateConnection(id);
+ connection.verifyCanDelete();
+ }
+
+ @Override
+ public void deleteConnection(final String id) {
+ final Connection connection = locateConnection(id);
+ connection.getProcessGroup().removeConnection(connection);
+ }
+
+ @Override
+ public DropFlowFileStatus deleteFlowFileDropRequest(String connectionId, String dropRequestId) {
+ final Connection connection = locateConnection(connectionId);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ final DropFlowFileStatus dropFlowFileStatus = queue.cancelDropFlowFileRequest(dropRequestId);
+ if (dropFlowFileStatus == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find drop request with id '%s'.", dropRequestId));
+ }
+
+ return dropFlowFileStatus;
+ }
+
+ @Override
+ public ListFlowFileStatus deleteFlowFileListingRequest(String connectionId, String listingRequestId) {
+ final Connection connection = locateConnection(connectionId);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+
+ final ListFlowFileStatus listFlowFileStatus = queue.cancelListFlowFileRequest(listingRequestId);
+ if (listFlowFileStatus == null) {
+ throw new ResourceNotFoundException(String.format("Unable to find listing request with id '%s'.", listingRequestId));
+ }
+
+ return listFlowFileStatus;
+ }
+
+ @Override
+ public DownloadableContent getContent(String id, String flowFileUuid, String requestUri) {
+ try {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final Connection connection = locateConnection(id);
+ final FlowFileQueue queue = connection.getFlowFileQueue();
+ final FlowFileRecord flowFile = queue.getFlowFile(flowFileUuid);
+
+ if (flowFile == null) {
+ throw new ResourceNotFoundException(String.format("The FlowFile with UUID %s is no longer in the active queue.", flowFileUuid));
+ }
+
+ // get the attributes and ensure appropriate access
+ final Map<String, String> attributes = flowFile.getAttributes();
+ final Authorizable dataAuthorizable = new DataAuthorizable(connection.getSourceAuthorizable());
+ dataAuthorizable.authorize(authorizer, RequestAction.READ, user, attributes);
+
+ // get the filename and fall back to the identifier (should never happen)
+ String filename = attributes.get(CoreAttributes.FILENAME.key());
+ if (filename == null) {
+ filename = flowFileUuid;
+ }
+
+ // get the mime-type
+ final String type = attributes.get(CoreAttributes.MIME_TYPE.key());
+
+ // get the content
+ final InputStream content = flowController.getContent(flowFile, user.getIdentity(), requestUri);
+ return new DownloadableContent(filename, type, content);
+ } catch (final ContentNotFoundException cnfe) {
+ throw new ResourceNotFoundException("Unable to find the specified content.");
+ } catch (final IOException ioe) {
+ logger.error(String.format("Unable to get the content for flowfile (%s) at this time.", flowFileUuid), ioe);
+ throw new IllegalStateException("Unable to get the content at this time.");
+ }
+ }
+
+ /* setters */
+ public void setFlowController(final FlowController flowController) {
+ this.flowController = flowController;
+ }
+
+ public void setAuthorizer(Authorizer authorizer) {
+ this.authorizer = authorizer;
+ }
+}
diff --git a/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/server/JettyServer.java b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/server/JettyServer.java
new file mode 100644
index 0000000..a3a1840
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -0,0 +1,1226 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+package org.apache.nifi.web.server;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.URI;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.ServletContext;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.NiFiServer;
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.controller.UninheritableFlowException;
+import org.apache.nifi.controller.serialization.FlowSerializationException;
+import org.apache.nifi.controller.serialization.FlowSynchronizationException;
+import org.apache.nifi.documentation.DocGenerator;
+import org.apache.nifi.lifecycle.LifeCycleStartException;
+import org.apache.nifi.nar.ExtensionDiscoveringManager;
+import org.apache.nifi.nar.ExtensionManagerHolder;
+import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.nar.ExtensionUiLoader;
+import org.apache.nifi.nar.NarAutoLoader;
+import org.apache.nifi.nar.DCAEAutoLoader;
+import org.apache.nifi.nar.NarClassLoadersHolder;
+import org.apache.nifi.nar.NarLoader;
+import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
+import org.apache.nifi.nar.StandardNarLoader;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.security.util.KeyStoreUtils;
+import org.apache.nifi.services.FlowService;
+import org.apache.nifi.ui.extension.UiExtension;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.ContentAccess;
+import org.apache.nifi.web.NiFiWebConfigurationContext;
+import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
+import org.apache.nifi.web.security.headers.StrictTransportSecurityFilter;
+import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
+import org.apache.nifi.web.security.headers.XSSProtectionFilter;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+/**
+ * Encapsulates the Jetty instance.
+ */
+public class JettyServer implements NiFiServer, ExtensionUiLoader {
+
+ private static final Logger logger = LoggerFactory.getLogger(JettyServer.class);
+ private static final String WEB_DEFAULTS_XML = "org/apache/nifi/web/webdefault.xml";
+
+ private static final String CONTAINER_INCLUDE_PATTERN_KEY = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
+ private static final String CONTAINER_INCLUDE_PATTERN_VALUE = ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\\\.jar$|.*/[^/]*taglibs.*\\.jar$";
+
+ private static final FileFilter WAR_FILTER = new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ final String nameToTest = pathname.getName().toLowerCase();
+ return nameToTest.endsWith(".war") && pathname.isFile();
+ }
+ };
+
+ private final Server server;
+ private final NiFiProperties props;
+
+ private Bundle systemBundle;
+ private Set<Bundle> bundles;
+ private ExtensionMapping extensionMapping;
+ private NarAutoLoader narAutoLoader;
+ private DCAEAutoLoader dcaeAutoLoader;
+
+ private WebAppContext webApiContext;
+ private WebAppContext webDocsContext;
+
+ // content viewer and mime type specific extensions
+ private WebAppContext webContentViewerContext;
+ private Collection<WebAppContext> contentViewerWebContexts;
+
+ // component (processor, controller service, reporting task) ui extensions
+ private UiExtensionMapping componentUiExtensions;
+ private Collection<WebAppContext> componentUiExtensionWebContexts;
+
+ private DeploymentManager deploymentManager;
+
+ public JettyServer(final NiFiProperties props, final Set<Bundle> bundles) {
+ final QueuedThreadPool threadPool = new QueuedThreadPool(props.getWebThreads());
+ threadPool.setName("NiFi Web Server");
+
+ // create the server
+ this.server = new Server(threadPool);
+ this.props = props;
+
+ // enable the annotation based configuration to ensure the jsp container is initialized properly
+ final Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
+ classlist.addBefore(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());
+
+ // configure server
+ configureConnectors(server);
+
+ // load wars from the bundle
+ final Handler warHandlers = loadInitialWars(bundles);
+
+ final HandlerList allHandlers = new HandlerList();
+
+ // Only restrict the host header if running in HTTPS mode
+ if (props.isHTTPSConfigured()) {
+ // Create a handler for the host header and add it to the server
+ HostHeaderHandler hostHeaderHandler = new HostHeaderHandler(props);
+ logger.info("Created HostHeaderHandler [" + hostHeaderHandler.toString() + "]");
+
+ // Add this before the WAR handlers
+ allHandlers.addHandler(hostHeaderHandler);
+ } else {
+ logger.info("Running in HTTP mode; host headers not restricted");
+ }
+
+
+ final ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
+ contextHandlers.addHandler(warHandlers);
+ allHandlers.addHandler(contextHandlers);
+ server.setHandler(allHandlers);
+
+ deploymentManager = new DeploymentManager();
+ deploymentManager.setContextAttribute(CONTAINER_INCLUDE_PATTERN_KEY, CONTAINER_INCLUDE_PATTERN_VALUE);
+ deploymentManager.setContexts(contextHandlers);
+ server.addBean(deploymentManager);
+ }
+
+ /**
+ * Instantiates this object but does not perform any configuration. Used for unit testing.
+ */
+ JettyServer(Server server, NiFiProperties properties) {
+ this.server = server;
+ this.props = properties;
+ }
+
+ private Handler loadInitialWars(final Set<Bundle> bundles) {
+
+ // load WARs
+ final Map<File, Bundle> warToBundleLookup = findWars(bundles);
+
+ // locate each war being deployed
+ File webUiWar = null;
+ File webApiWar = null;
+ File webErrorWar = null;
+ File webDocsWar = null;
+ File webContentViewerWar = null;
+ Map<File, Bundle> otherWars = new HashMap<>();
+ for (Map.Entry<File,Bundle> warBundleEntry : warToBundleLookup.entrySet()) {
+ final File war = warBundleEntry.getKey();
+ final Bundle warBundle = warBundleEntry.getValue();
+
+ if (war.getName().toLowerCase().startsWith("nifi-web-api")) {
+ webApiWar = war;
+ } else if (war.getName().toLowerCase().startsWith("nifi-web-error")) {
+ webErrorWar = war;
+ } else if (war.getName().toLowerCase().startsWith("nifi-web-docs")) {
+ webDocsWar = war;
+ } else if (war.getName().toLowerCase().startsWith("nifi-web-content-viewer")) {
+ webContentViewerWar = war;
+ } else if (war.getName().toLowerCase().startsWith("nifi-web")) {
+ webUiWar = war;
+ } else {
+ otherWars.put(war, warBundle);
+ }
+ }
+
+ // ensure the required wars were found
+ if (webUiWar == null) {
+ throw new RuntimeException("Unable to load nifi-web WAR");
+ } else if (webApiWar == null) {
+ throw new RuntimeException("Unable to load nifi-web-api WAR");
+ } else if (webDocsWar == null) {
+ throw new RuntimeException("Unable to load nifi-web-docs WAR");
+ } else if (webErrorWar == null) {
+ throw new RuntimeException("Unable to load nifi-web-error WAR");
+ } else if (webContentViewerWar == null) {
+ throw new RuntimeException("Unable to load nifi-web-content-viewer WAR");
+ }
+
+ // handlers for each war and init params for the web api
+ final ExtensionUiInfo extensionUiInfo = loadWars(otherWars);
+ componentUiExtensionWebContexts = new ArrayList<>(extensionUiInfo.getComponentUiExtensionWebContexts());
+ contentViewerWebContexts = new ArrayList<>(extensionUiInfo.getContentViewerWebContexts());
+ componentUiExtensions = new UiExtensionMapping(extensionUiInfo.getComponentUiExtensionsByType());
+
+ final HandlerCollection webAppContextHandlers = new HandlerCollection();
+ final Collection<WebAppContext> extensionUiContexts = extensionUiInfo.getWebAppContexts();
+ extensionUiContexts.stream().forEach(c -> webAppContextHandlers.addHandler(c));
+
+ final ClassLoader frameworkClassLoader = getClass().getClassLoader();
+
+ // load the web ui app
+ final WebAppContext webUiContext = loadWar(webUiWar, "/nifi", frameworkClassLoader);
+ webUiContext.getInitParams().put("oidc-supported", String.valueOf(props.isOidcEnabled()));
+ webUiContext.getInitParams().put("knox-supported", String.valueOf(props.isKnoxSsoEnabled()));
+ webUiContext.getInitParams().put("whitelistedContextPaths", props.getWhitelistedContextPaths());
+ webAppContextHandlers.addHandler(webUiContext);
+
+ // load the web api app
+ webApiContext = loadWar(webApiWar, "/nifi-api", frameworkClassLoader);
+ webAppContextHandlers.addHandler(webApiContext);
+
+ // load the content viewer app
+ webContentViewerContext = loadWar(webContentViewerWar, "/nifi-content-viewer", frameworkClassLoader);
+ webContentViewerContext.getInitParams().putAll(extensionUiInfo.getMimeMappings());
+ webAppContextHandlers.addHandler(webContentViewerContext);
+
+ // create a web app for the docs
+ final String docsContextPath = "/nifi-docs";
+
+ // load the documentation war
+ webDocsContext = loadWar(webDocsWar, docsContextPath, frameworkClassLoader);
+
+ // add the servlets which serve the HTML documentation within the documentation web app
+ addDocsServlets(webDocsContext);
+
+ webAppContextHandlers.addHandler(webDocsContext);
+
+ // load the web error app
+ final WebAppContext webErrorContext = loadWar(webErrorWar, "/", frameworkClassLoader);
+ webErrorContext.getInitParams().put("whitelistedContextPaths", props.getWhitelistedContextPaths());
+ webAppContextHandlers.addHandler(webErrorContext);
+
+ // deploy the web apps
+ return gzip(webAppContextHandlers);
+ }
+
+ @Override
+ public void loadExtensionUis(final Set<Bundle> bundles) {
+ // Find and load any WARs contained within the set of bundles...
+ final Map<File, Bundle> warToBundleLookup = findWars(bundles);
+ final ExtensionUiInfo extensionUiInfo = loadWars(warToBundleLookup);
+
+ final Collection<WebAppContext> webAppContexts = extensionUiInfo.getWebAppContexts();
+ if (CollectionUtils.isEmpty(webAppContexts)) {
+ logger.debug("No webapp contexts were loaded, returning...");
+ return;
+ }
+
+ // Deploy each WAR that was loaded...
+ for (final WebAppContext webAppContext : webAppContexts) {
+ final App extensionUiApp = new App(deploymentManager, null, "nifi-jetty-server", webAppContext);
+ deploymentManager.addApp(extensionUiApp);
+ }
+
+ final Collection<WebAppContext> componentUiExtensionWebContexts = extensionUiInfo.getComponentUiExtensionWebContexts();
+ final Collection<WebAppContext> contentViewerWebContexts = extensionUiInfo.getContentViewerWebContexts();
+
+ // Inject the configuration context and security filter into contexts that need it
+ final ServletContext webApiServletContext = webApiContext.getServletHandler().getServletContext();
+ final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(webApiServletContext);
+ final NiFiWebConfigurationContext configurationContext = webApplicationContext.getBean("nifiWebConfigurationContext", NiFiWebConfigurationContext.class);
+ final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter("springSecurityFilterChain");
+
+ performInjectionForComponentUis(componentUiExtensionWebContexts, configurationContext, securityFilter);
+ performInjectionForContentViewerUis(contentViewerWebContexts, securityFilter);
+
+ // Merge results of current loading into previously loaded results...
+ this.componentUiExtensionWebContexts.addAll(componentUiExtensionWebContexts);
+ this.contentViewerWebContexts.addAll(contentViewerWebContexts);
+ this.componentUiExtensions.addUiExtensions(extensionUiInfo.getComponentUiExtensionsByType());
+
+ for (final WebAppContext webAppContext : webAppContexts) {
+ final Throwable t = webAppContext.getUnavailableException();
+ if (t != null) {
+ logger.error("Unable to start context due to " + t.getMessage(), t);
+ }
+ }
+ }
+
+ private ExtensionUiInfo loadWars(final Map<File, Bundle> warToBundleLookup) {
+ // handlers for each war and init params for the web api
+ final List<WebAppContext> webAppContexts = new ArrayList<>();
+ final Map<String, String> mimeMappings = new HashMap<>();
+ final Collection<WebAppContext> componentUiExtensionWebContexts = new ArrayList<>();
+ final Collection<WebAppContext> contentViewerWebContexts = new ArrayList<>();
+ final Map<String, List<UiExtension>> componentUiExtensionsByType = new HashMap<>();
+
+ final ClassLoader frameworkClassLoader = getClass().getClassLoader();
+ final ClassLoader jettyClassLoader = frameworkClassLoader.getParent();
+
+ // deploy the other wars
+ if (!warToBundleLookup.isEmpty()) {
+ // ui extension organized by component type
+ for (Map.Entry<File,Bundle> warBundleEntry : warToBundleLookup.entrySet()) {
+ final File war = warBundleEntry.getKey();
+ final Bundle warBundle = warBundleEntry.getValue();
+
+ // identify all known extension types in the war
+ final Map<UiExtensionType, List<String>> uiExtensionInWar = new HashMap<>();
+ identifyUiExtensionsForComponents(uiExtensionInWar, war);
+
+ // only include wars that are for custom processor ui's
+ if (!uiExtensionInWar.isEmpty()) {
+ // get the context path
+ String warName = StringUtils.substringBeforeLast(war.getName(), ".");
+ String warContextPath = String.format("/%s", warName);
+
+ // get the classloader for this war
+ ClassLoader narClassLoaderForWar = warBundle.getClassLoader();
+
+ // this should never be null
+ if (narClassLoaderForWar == null) {
+ narClassLoaderForWar = jettyClassLoader;
+ }
+
+ // create the extension web app context
+ WebAppContext extensionUiContext = loadWar(war, warContextPath, narClassLoaderForWar);
+
+ // create the ui extensions
+ for (final Map.Entry<UiExtensionType, List<String>> entry : uiExtensionInWar.entrySet()) {
+ final UiExtensionType extensionType = entry.getKey();
+ final List<String> types = entry.getValue();
+
+ if (UiExtensionType.ContentViewer.equals(extensionType)) {
+ // consider each content type identified
+ for (final String contentType : types) {
+ // map the content type to the context path
+ mimeMappings.put(contentType, warContextPath);
+ }
+
+ // this ui extension provides a content viewer
+ contentViewerWebContexts.add(extensionUiContext);
+ } else {
+ // consider each component type identified
+ for (final String componentTypeCoordinates : types) {
+ logger.info(String.format("Loading UI extension [%s, %s] for %s", extensionType, warContextPath, componentTypeCoordinates));
+
+ // record the extension definition
+ final UiExtension uiExtension = new UiExtension(extensionType, warContextPath);
+
+ // create if this is the first extension for this component type
+ List<UiExtension> componentUiExtensionsForType = componentUiExtensionsByType.get(componentTypeCoordinates);
+ if (componentUiExtensionsForType == null) {
+ componentUiExtensionsForType = new ArrayList<>();
+ componentUiExtensionsByType.put(componentTypeCoordinates, componentUiExtensionsForType);
+ }
+
+ // see if there is already a ui extension of this same time
+ if (containsUiExtensionType(componentUiExtensionsForType, extensionType)) {
+ throw new IllegalStateException(String.format("Encountered duplicate UI for %s", componentTypeCoordinates));
+ }
+
+ // record this extension
+ componentUiExtensionsForType.add(uiExtension);
+ }
+
+ // this ui extension provides a component custom ui
+ componentUiExtensionWebContexts.add(extensionUiContext);
+ }
+ }
+
+ // include custom ui web context in the handlers
+ webAppContexts.add(extensionUiContext);
+ }
+ }
+ }
+
+ return new ExtensionUiInfo(webAppContexts, mimeMappings, componentUiExtensionWebContexts, contentViewerWebContexts, componentUiExtensionsByType);
+ }
+
+ /**
+ * Returns whether or not the specified ui extensions already contains an extension of the specified type.
+ *
+ * @param componentUiExtensionsForType ui extensions for the type
+ * @param extensionType type of ui extension
+ * @return whether or not the specified ui extensions already contains an extension of the specified type
+ */
+ private boolean containsUiExtensionType(final List<UiExtension> componentUiExtensionsForType, final UiExtensionType extensionType) {
+ for (final UiExtension uiExtension : componentUiExtensionsForType) {
+ if (extensionType.equals(uiExtension.getExtensionType())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Enables compression for the specified handler.
+ *
+ * @param handler handler to enable compression for
+ * @return compression enabled handler
+ */
+ private Handler gzip(final Handler handler) {
+ final GzipHandler gzip = new GzipHandler();
+ gzip.setIncludedMethods("GET", "POST", "PUT", "DELETE");
+ gzip.setHandler(handler);
+ return gzip;
+ }
+
+ private Map<File, Bundle> findWars(final Set<Bundle> bundles) {
+ final Map<File, Bundle> wars = new HashMap<>();
+
+ // consider each nar working directory
+ bundles.forEach(bundle -> {
+ final BundleDetails details = bundle.getBundleDetails();
+ final File narDependencies = new File(details.getWorkingDirectory(), "NAR-INF/bundled-dependencies");
+ if (narDependencies.isDirectory()) {
+ // list the wars from this nar
+ final File[] narDependencyDirs = narDependencies.listFiles(WAR_FILTER);
+ if (narDependencyDirs == null) {
+ throw new IllegalStateException(String.format("Unable to access working directory for NAR dependencies in: %s", narDependencies.getAbsolutePath()));
+ }
+
+ // add each war
+ for (final File war : narDependencyDirs) {
+ wars.put(war, bundle);
+ }
+ }
+ });
+
+ return wars;
+ }
+
+ private void readUiExtensions(final Map<UiExtensionType, List<String>> uiExtensions, final UiExtensionType uiExtensionType, final JarFile jarFile, final JarEntry jarEntry) throws IOException {
+ if (jarEntry == null) {
+ return;
+ }
+
+ // get an input stream for the nifi-processor configuration file
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(jarFile.getInputStream(jarEntry)))) {
+
+ // read in each configured type
+ String rawComponentType;
+ while ((rawComponentType = in.readLine()) != null) {
+ // extract the component type
+ final String componentType = extractComponentType(rawComponentType);
+ if (componentType != null) {
+ List<String> extensions = uiExtensions.get(uiExtensionType);
+
+ // if there are currently no extensions for this type create it
+ if (extensions == null) {
+ extensions = new ArrayList<>();
+ uiExtensions.put(uiExtensionType, extensions);
+ }
+
+ // add the specified type
+ extensions.add(componentType);
+ }
+ }
+ }
+ }
+
+ /**
+ * Identifies all known UI extensions and stores them in the specified map.
+ *
+ * @param uiExtensions extensions
+ * @param warFile war
+ */
+ private void identifyUiExtensionsForComponents(final Map<UiExtensionType, List<String>> uiExtensions, final File warFile) {
+ try (final JarFile jarFile = new JarFile(warFile)) {
+ // locate the ui extensions
+ readUiExtensions(uiExtensions, UiExtensionType.ContentViewer, jarFile, jarFile.getJarEntry("META-INF/nifi-content-viewer"));
+ readUiExtensions(uiExtensions, UiExtensionType.ProcessorConfiguration, jarFile, jarFile.getJarEntry("META-INF/nifi-processor-configuration"));
+ readUiExtensions(uiExtensions, UiExtensionType.ControllerServiceConfiguration, jarFile, jarFile.getJarEntry("META-INF/nifi-controller-service-configuration"));
+ readUiExtensions(uiExtensions, UiExtensionType.ReportingTaskConfiguration, jarFile, jarFile.getJarEntry("META-INF/nifi-reporting-task-configuration"));
+ } catch (IOException ioe) {
+ logger.warn(String.format("Unable to inspect %s for a UI extensions.", warFile));
+ }
+ }
+
+ /**
+ * Extracts the component type. Trims the line and considers comments.
+ * Returns null if no type was found.
+ *
+ * @param line line
+ * @return type
+ */
+ private String extractComponentType(final String line) {
+ final String trimmedLine = line.trim();
+ if (!trimmedLine.isEmpty() && !trimmedLine.startsWith("#")) {
+ final int indexOfPound = trimmedLine.indexOf("#");
+ return (indexOfPound > 0) ? trimmedLine.substring(0, indexOfPound) : trimmedLine;
+ }
+ return null;
+ }
+
+ private WebAppContext loadWar(final File warFile, final String contextPath, final ClassLoader parentClassLoader) {
+ final WebAppContext webappContext = new WebAppContext(warFile.getPath(), contextPath);
+ webappContext.setContextPath(contextPath);
+ webappContext.setDisplayName(contextPath);
+
+ // instruction jetty to examine these jars for tlds, web-fragments, etc
+ webappContext.setAttribute(CONTAINER_INCLUDE_PATTERN_KEY, CONTAINER_INCLUDE_PATTERN_VALUE);
+
+ // remove slf4j server class to allow WAR files to have slf4j dependencies in WEB-INF/lib
+ List<String> serverClasses = new ArrayList<>(Arrays.asList(webappContext.getServerClasses()));
+ serverClasses.remove("org.slf4j.");
+ webappContext.setServerClasses(serverClasses.toArray(new String[0]));
+ webappContext.setDefaultsDescriptor(WEB_DEFAULTS_XML);
+
+ // get the temp directory for this webapp
+ File tempDir = new File(props.getWebWorkingDirectory(), warFile.getName());
+ if (tempDir.exists() && !tempDir.isDirectory()) {
+ throw new RuntimeException(tempDir.getAbsolutePath() + " is not a directory");
+ } else if (!tempDir.exists()) {
+ final boolean made = tempDir.mkdirs();
+ if (!made) {
+ throw new RuntimeException(tempDir.getAbsolutePath() + " could not be created");
+ }
+ }
+ if (!(tempDir.canRead() && tempDir.canWrite())) {
+ throw new RuntimeException(tempDir.getAbsolutePath() + " directory does not have read/write privilege");
+ }
+
+ // configure the temp dir
+ webappContext.setTempDirectory(tempDir);
+
+ // configure the max form size (3x the default)
+ webappContext.setMaxFormContentSize(600000);
+
+ // add HTTP security headers to all responses
+ final String ALL_PATHS = "/*";
+ ArrayList<Class<? extends Filter>> filters = new ArrayList<>(Arrays.asList(XFrameOptionsFilter.class, ContentSecurityPolicyFilter.class, XSSProtectionFilter.class));
+ if(props.isHTTPSConfigured()) {
+ filters.add(StrictTransportSecurityFilter.class);
+ }
+ filters.forEach( (filter) -> addFilters(filter, ALL_PATHS, webappContext));
+
+ try {
+ // configure the class loader - webappClassLoader -> jetty nar -> web app's nar -> ...
+ webappContext.setClassLoader(new WebAppClassLoader(parentClassLoader, webappContext));
+ } catch (final IOException ioe) {
+ startUpFailure(ioe);
+ }
+
+ logger.info("Loading WAR: " + warFile.getAbsolutePath() + " with context path set to " + contextPath);
+ return webappContext;
+ }
+
+ private void addFilters(Class<? extends Filter> clazz, String path, WebAppContext webappContext) {
+ FilterHolder holder = new FilterHolder(clazz);
+ holder.setName(clazz.getSimpleName());
+ webappContext.addFilter(holder, path, EnumSet.allOf(DispatcherType.class));
+ }
+
+ private void addDocsServlets(WebAppContext docsContext) {
+ try {
+ // Load the nifi/docs directory
+ final File docsDir = getDocsDir("docs");
+
+ // load the component documentation working directory
+ final File componentDocsDirPath = props.getComponentDocumentationWorkingDirectory();
+ final File workingDocsDirectory = getWorkingDocsDirectory(componentDocsDirPath);
+
+ // Load the API docs
+ final File webApiDocsDir = getWebApiDocsDir();
+
+ // Create the servlet which will serve the static resources
+ ServletHolder defaultHolder = new ServletHolder("default", DefaultServlet.class);
+ defaultHolder.setInitParameter("dirAllowed", "false");
+
+ ServletHolder docs = new ServletHolder("docs", DefaultServlet.class);
+ docs.setInitParameter("resourceBase", docsDir.getPath());
+
+ ServletHolder components = new ServletHolder("components", DefaultServlet.class);
+ components.setInitParameter("resourceBase", workingDocsDirectory.getPath());
+
+ ServletHolder restApi = new ServletHolder("rest-api", DefaultServlet.class);
+ restApi.setInitParameter("resourceBase", webApiDocsDir.getPath());
+
+ docsContext.addServlet(docs, "/html/*");
+ docsContext.addServlet(components, "/components/*");
+ docsContext.addServlet(restApi, "/rest-api/*");
+
+ docsContext.addServlet(defaultHolder, "/");
+
+ logger.info("Loading documents web app with context path set to " + docsContext.getContextPath());
+
+ } catch (Exception ex) {
+ logger.error("Unhandled Exception in createDocsWebApp: " + ex.getMessage());
+ startUpFailure(ex);
+ }
+ }
+
+
+ /**
+ * Returns a File object for the directory containing NIFI documentation.
+ * <p>
+ * Formerly, if the docsDirectory did not exist NIFI would fail to start
+ * with an IllegalStateException and a rather unhelpful log message.
+ * NIFI-2184 updates the process such that if the docsDirectory does not
+ * exist an attempt will be made to create the directory. If that is
+ * successful NIFI will no longer fail and will start successfully barring
+ * any other errors. The side effect of the docsDirectory not being present
+ * is that the documentation links under the 'General' portion of the help
+ * page will not be accessible, but at least the process will be running.
+ *
+ * @param docsDirectory Name of documentation directory in installation directory.
+ * @return A File object to the documentation directory; else startUpFailure called.
+ */
+ private File getDocsDir(final String docsDirectory) {
+ File docsDir;
+ try {
+ docsDir = Paths.get(docsDirectory).toRealPath().toFile();
+ } catch (IOException ex) {
+ logger.info("Directory '" + docsDirectory + "' is missing. Some documentation will be unavailable.");
+ docsDir = new File(docsDirectory).getAbsoluteFile();
+ final boolean made = docsDir.mkdirs();
+ if (!made) {
+ logger.error("Failed to create 'docs' directory!");
+ startUpFailure(new IOException(docsDir.getAbsolutePath() + " could not be created"));
+ }
+ }
+ return docsDir;
+ }
+
+ private File getWorkingDocsDirectory(final File componentDocsDirPath) {
+ File workingDocsDirectory = null;
+ try {
+ workingDocsDirectory = componentDocsDirPath.toPath().toRealPath().getParent().toFile();
+ } catch (IOException ex) {
+ logger.error("Failed to load :" + componentDocsDirPath.getAbsolutePath());
+ startUpFailure(ex);
+ }
+ return workingDocsDirectory;
+ }
+
+ private File getWebApiDocsDir() {
+ // load the rest documentation
+ final File webApiDocsDir = new File(webApiContext.getTempDirectory(), "webapp/docs");
+ if (!webApiDocsDir.exists()) {
+ final boolean made = webApiDocsDir.mkdirs();
+ if (!made) {
+ logger.error("Failed to create " + webApiDocsDir.getAbsolutePath());
+ startUpFailure(new IOException(webApiDocsDir.getAbsolutePath() + " could not be created"));
+ }
+ }
+ return webApiDocsDir;
+ }
+
+ private void configureConnectors(final Server server) throws ServerConfigurationException {
+ // create the http configuration
+ final HttpConfiguration httpConfiguration = new HttpConfiguration();
+ final int headerSize = DataUnit.parseDataSize(props.getWebMaxHeaderSize(), DataUnit.B).intValue();
+ httpConfiguration.setRequestHeaderSize(headerSize);
+ httpConfiguration.setResponseHeaderSize(headerSize);
+
+ // Check if both HTTP and HTTPS connectors are configured and fail if both are configured
+ if (bothHttpAndHttpsConnectorsConfigured(props)) {
+ logger.error("NiFi only supports one mode of HTTP or HTTPS operation, not both simultaneously. " +
+ "Check the nifi.properties file and ensure that either the HTTP hostname and port or the HTTPS hostname and port are empty");
+ startUpFailure(new IllegalStateException("Only one of the HTTP and HTTPS connectors can be configured at one time"));
+ }
+
+ if (props.getSslPort() != null) {
+ configureHttpsConnector(server, httpConfiguration);
+ } else if (props.getPort() != null) {
+ configureHttpConnector(server, httpConfiguration);
+ } else {
+ logger.error("Neither the HTTP nor HTTPS connector was configured in nifi.properties");
+ startUpFailure(new IllegalStateException("Must configure HTTP or HTTPS connector"));
+ }
+ }
+
+ /**
+ * Configures an HTTPS connector and adds it to the server.
+ *
+ * @param server the Jetty server instance
+ * @param httpConfiguration the configuration object for the HTTPS protocol settings
+ */
+ private void configureHttpsConnector(Server server, HttpConfiguration httpConfiguration) {
+ String hostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
+ final Integer port = props.getSslPort();
+ String connectorLabel = "HTTPS";
+ final Map<String, String> httpsNetworkInterfaces = props.getHttpsNetworkInterfaces();
+ ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> createUnconfiguredSslServerConnector(s, c, port);
+
+ configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpsNetworkInterfaces, scc);
+ }
+
+ /**
+ * Configures an HTTP connector and adds it to the server.
+ *
+ * @param server the Jetty server instance
+ * @param httpConfiguration the configuration object for the HTTP protocol settings
+ */
+ private void configureHttpConnector(Server server, HttpConfiguration httpConfiguration) {
+ String hostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+ final Integer port = props.getPort();
+ String connectorLabel = "HTTP";
+ final Map<String, String> httpNetworkInterfaces = props.getHttpNetworkInterfaces();
+ ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> new ServerConnector(s, new HttpConnectionFactory(c));
+
+ configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpNetworkInterfaces, scc);
+ }
+
+ /**
+ * Configures an HTTP(S) connector for the server given the provided parameters. The functionality between HTTP and HTTPS connectors is largely similar.
+ * Here the common behavior has been extracted into a shared method and the respective calling methods obtain the right values and a lambda function for the differing behavior.
+ *
+ * @param server the Jetty server instance
+ * @param configuration the HTTP/HTTPS configuration instance
+ * @param hostname the hostname from the nifi.properties file
+ * @param port the port to expose
+ * @param connectorLabel used for log output (e.g. "HTTP" or "HTTPS")
+ * @param networkInterfaces the map of network interfaces from nifi.properties
+ * @param serverConnectorCreator a function which accepts a {@code Server} and {@code HttpConnection} instance and returns a {@code ServerConnector}
+ */
+ private void configureGenericConnector(Server server, HttpConfiguration configuration, String hostname, Integer port, String connectorLabel, Map<String, String> networkInterfaces,
+ ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> serverConnectorCreator) {
+ if (port < 0 || (int) Math.pow(2, 16) <= port) {
+ throw new ServerConfigurationException("Invalid " + connectorLabel + " port: " + port);
+ }
+
+ logger.info("Configuring Jetty for " + connectorLabel + " on port: " + port);
+
+ final List<Connector> serverConnectors = Lists.newArrayList();
+
+ // Calculate Idle Timeout as twice the auto-refresh interval. This ensures that even with some variance in timing,
+ // we are able to avoid closing connections from users' browsers most of the time. This can make a significant difference
+ // in HTTPS connections, as each HTTPS connection that is established must perform the SSL handshake.
+ final String autoRefreshInterval = props.getAutoRefreshInterval();
+ final long autoRefreshMillis = autoRefreshInterval == null ? 30000L : FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.MILLISECONDS);
+ final long idleTimeout = autoRefreshMillis * 2;
+
+ // If the interfaces collection is empty or each element is empty
+ if (networkInterfaces.isEmpty() || networkInterfaces.values().stream().filter(value -> !Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) {
+ final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
+
+ // Set host and port
+ if (StringUtils.isNotBlank(hostname)) {
+ serverConnector.setHost(hostname);
+ }
+ serverConnector.setPort(port);
+ serverConnector.setIdleTimeout(idleTimeout);
+ serverConnectors.add(serverConnector);
+ } else {
+ // Add connectors for all IPs from network interfaces
+ serverConnectors.addAll(Lists.newArrayList(networkInterfaces.values().stream().map(ifaceName -> {
+ NetworkInterface iface = null;
+ try {
+ iface = NetworkInterface.getByName(ifaceName);
+ } catch (SocketException e) {
+ logger.error("Unable to get network interface by name {}", ifaceName, e);
+ }
+ if (iface == null) {
+ logger.warn("Unable to find network interface named {}", ifaceName);
+ }
+ return iface;
+ }).filter(Objects::nonNull).flatMap(iface -> Collections.list(iface.getInetAddresses()).stream())
+ .map(inetAddress -> {
+ final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
+
+ // Set host and port
+ serverConnector.setHost(inetAddress.getHostAddress());
+ serverConnector.setPort(port);
+ serverConnector.setIdleTimeout(idleTimeout);
+
+ return serverConnector;
+ }).collect(Collectors.toList())));
+ }
+ // Add all connectors
+ serverConnectors.forEach(server::addConnector);
+ }
+
+ /**
+ * Returns true if there are configured properties for both HTTP and HTTPS connectors (specifically port because the hostname can be left blank in the HTTP connector).
+ * Prints a warning log message with the relevant properties.
+ *
+ * @param props the NiFiProperties
+ * @return true if both ports are present
+ */
+ static boolean bothHttpAndHttpsConnectorsConfigured(NiFiProperties props) {
+ Integer httpPort = props.getPort();
+ String httpHostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+
+ Integer httpsPort = props.getSslPort();
+ String httpsHostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
+
+ if (httpPort != null && httpsPort != null) {
+ logger.warn("Both the HTTP and HTTPS connectors are configured in nifi.properties. Only one of these connectors should be configured. See the NiFi Admin Guide for more details");
+ logger.warn("HTTP connector: http://" + httpHostname + ":" + httpPort);
+ logger.warn("HTTPS connector: https://" + httpsHostname + ":" + httpsPort);
+ return true;
+ }
+
+ return false;
+ }
+
+ private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration, int port) {
+ // add some secure config
+ final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
+ httpsConfiguration.setSecureScheme("https");
+ httpsConfiguration.setSecurePort(port);
+ httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
+
+ // build the connector
+ return new ServerConnector(server,
+ new SslConnectionFactory(createSslContextFactory(), "http/1.1"),
+ new HttpConnectionFactory(httpsConfiguration));
+ }
+
+ private SslContextFactory createSslContextFactory() {
+ final SslContextFactory contextFactory = new SslContextFactory();
+ configureSslContextFactory(contextFactory, props);
+ return contextFactory;
+ }
+
+ protected static void configureSslContextFactory(SslContextFactory contextFactory, NiFiProperties props) {
+ // require client auth when not supporting login, Kerberos service, or anonymous access
+ if (props.isClientAuthRequiredForRestApi()) {
+ contextFactory.setNeedClientAuth(true);
+ } else {
+ contextFactory.setWantClientAuth(true);
+ }
+
+ /* below code sets JSSE system properties when values are provided */
+ // keystore properties
+ if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE))) {
+ contextFactory.setKeyStorePath(props.getProperty(NiFiProperties.SECURITY_KEYSTORE));
+ }
+ String keyStoreType = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
+ if (StringUtils.isNotBlank(keyStoreType)) {
+ contextFactory.setKeyStoreType(keyStoreType);
+ String keyStoreProvider = KeyStoreUtils.getKeyStoreProvider(keyStoreType);
+ if (StringUtils.isNoneEmpty(keyStoreProvider)) {
+ contextFactory.setKeyStoreProvider(keyStoreProvider);
+ }
+ }
+ final String keystorePassword = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD);
+ final String keyPassword = props.getProperty(NiFiProperties.SECURITY_KEY_PASSWD);
+ if (StringUtils.isNotBlank(keystorePassword)) {
+ // if no key password was provided, then assume the keystore password is the same as the key password.
+ final String defaultKeyPassword = (StringUtils.isBlank(keyPassword)) ? keystorePassword : keyPassword;
+ contextFactory.setKeyStorePassword(keystorePassword);
+ contextFactory.setKeyManagerPassword(defaultKeyPassword);
+ } else if (StringUtils.isNotBlank(keyPassword)) {
+ // since no keystore password was provided, there will be no keystore integrity check
+ contextFactory.setKeyManagerPassword(keyPassword);
+ }
+
+ // truststore properties
+ if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))) {
+ contextFactory.setTrustStorePath(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
+ }
+ String trustStoreType = props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
+ if (StringUtils.isNotBlank(trustStoreType)) {
+ contextFactory.setTrustStoreType(trustStoreType);
+ String trustStoreProvider = KeyStoreUtils.getKeyStoreProvider(trustStoreType);
+ if (StringUtils.isNoneEmpty(trustStoreProvider)) {
+ contextFactory.setTrustStoreProvider(trustStoreProvider);
+ }
+ }
+ if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD))) {
+ contextFactory.setTrustStorePassword(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
+ }
+ }
+
+ @Override
+ public void start() {
+ try {
+ // Create a standard extension manager and discover extensions
+ final ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
+ extensionManager.discoverExtensions(systemBundle, bundles);
+ extensionManager.logClassLoaderMapping();
+
+ // Set the extension manager into the holder which makes it available to the Spring context via a factory bean
+ ExtensionManagerHolder.init(extensionManager);
+
+ // Generate docs for extensions
+ DocGenerator.generate(props, extensionManager, extensionMapping);
+
+ // start the server
+ server.start();
+
+ // ensure everything started successfully
+ for (Handler handler : server.getChildHandlers()) {
+ // see if the handler is a web app
+ if (handler instanceof WebAppContext) {
+ WebAppContext context = (WebAppContext) handler;
+
+ // see if this webapp had any exceptions that would
+ // cause it to be unavailable
+ if (context.getUnavailableException() != null) {
+ startUpFailure(context.getUnavailableException());
+ }
+ }
+ }
+
+ // ensure the appropriate wars deployed successfully before injecting the NiFi context and security filters
+ // this must be done after starting the server (and ensuring there were no start up failures)
+ if (webApiContext != null) {
+ // give the web api the component ui extensions
+ final ServletContext webApiServletContext = webApiContext.getServletHandler().getServletContext();
+ webApiServletContext.setAttribute("nifi-ui-extensions", componentUiExtensions);
+
+ // get the application context
+ final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(webApiServletContext);
+ final NiFiWebConfigurationContext configurationContext = webApplicationContext.getBean("nifiWebConfigurationContext", NiFiWebConfigurationContext.class);
+ final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter("springSecurityFilterChain");
+
+ // component ui extensions
+ performInjectionForComponentUis(componentUiExtensionWebContexts, configurationContext, securityFilter);
+
+ // content viewer extensions
+ performInjectionForContentViewerUis(contentViewerWebContexts, securityFilter);
+
+ // content viewer controller
+ if (webContentViewerContext != null) {
+ final ContentAccess contentAccess = webApplicationContext.getBean("contentAccess", ContentAccess.class);
+
+ // add the content access
+ final ServletContext webContentViewerServletContext = webContentViewerContext.getServletHandler().getServletContext();
+ webContentViewerServletContext.setAttribute("nifi-content-access", contentAccess);
+
+ if (securityFilter != null) {
+ webContentViewerContext.addFilter(securityFilter, "/*", EnumSet.allOf(DispatcherType.class));
+ }
+ }
+ }
+
+ // ensure the web document war was loaded and provide the extension mapping
+ if (webDocsContext != null) {
+ final ServletContext webDocsServletContext = webDocsContext.getServletHandler().getServletContext();
+ webDocsServletContext.setAttribute("nifi-extension-mapping", extensionMapping);
+ }
+
+ // if this nifi is a node in a cluster, start the flow service and load the flow - the
+ // flow service is loaded here for clustered nodes because the loading of the flow will
+ // initialize the connection between the node and the NCM. if the node connects (starts
+ // heartbeating, etc), the NCM may issue web requests before the application (wars) have
+ // finished loading. this results in the node being disconnected since its unable to
+ // successfully respond to the requests. to resolve this, flow loading was moved to here
+ // (after the wars have been successfully deployed) when this nifi instance is a node
+ // in a cluster
+ if (props.isNode()) {
+
+ FlowService flowService = null;
+ try {
+
+ logger.info("Loading Flow...");
+
+ ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(webApiContext.getServletContext());
+ flowService = ctx.getBean("flowService", FlowService.class);
+
+ // start and load the flow
+ flowService.start();
+ flowService.load(null);
+
+ logger.info("Flow loaded successfully.");
+
+ } catch (BeansException | LifeCycleStartException | IOException | FlowSerializationException | FlowSynchronizationException | UninheritableFlowException e) {
+ // ensure the flow service is terminated
+ if (flowService != null && flowService.isRunning()) {
+ flowService.stop(false);
+ }
+ logger.error("Unable to load flow due to: " + e, e);
+ throw new Exception("Unable to load flow due to: " + e); // cannot wrap the exception as they are not defined in a classloader accessible to the caller
+ }
+ }
+
+ final NarLoader narLoader = new StandardNarLoader(
+ props.getExtensionsWorkingDirectory(),
+ props.getComponentDocumentationWorkingDirectory(),
+ NarClassLoadersHolder.getInstance(),
+ extensionManager,
+ extensionMapping,
+ this);
+
+ narAutoLoader = new NarAutoLoader(props.getNarAutoLoadDirectory(), narLoader);
+ narAutoLoader.start();
+
+ URI jarsIndex = props.getDCAEJarIndexURI();
+
+ // REVIEW: Added ability to turn off the loaidng of dcae jars by providing no url
+ if (jarsIndex == null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Auto-loading of DCAE jars is turned off.");
+ sb.append(" You must set the value of \"nifi.dcae.jars.index.url\"");
+ sb.append(" to the full url to the index JSON of DCAE jars in the nifi.properties file");
+ sb.append(" in order to activate this feature.");
+ logger.warn(sb.toString());
+ } else {
+ this.dcaeAutoLoader = new DCAEAutoLoader();
+ this.dcaeAutoLoader.start(jarsIndex, extensionManager);
+ }
+
+ // dump the application url after confirming everything started successfully
+ dumpUrls();
+ } catch (Exception ex) {
+ startUpFailure(ex);
+ }
+ }
+
+ private void performInjectionForComponentUis(final Collection<WebAppContext> componentUiExtensionWebContexts,
+ final NiFiWebConfigurationContext configurationContext, final FilterHolder securityFilter) {
+ if (CollectionUtils.isNotEmpty(componentUiExtensionWebContexts)) {
+ for (final WebAppContext customUiContext : componentUiExtensionWebContexts) {
+ // set the NiFi context in each custom ui servlet context
+ final ServletContext customUiServletContext = customUiContext.getServletHandler().getServletContext();
+ customUiServletContext.setAttribute("nifi-web-configuration-context", configurationContext);
+
+ // add the security filter to any ui extensions wars
+ if (securityFilter != null) {
+ customUiContext.addFilter(securityFilter, "/*", EnumSet.allOf(DispatcherType.class));
+ }
+ }
+ }
+ }
+
+ private void performInjectionForContentViewerUis(final Collection<WebAppContext> contentViewerWebContexts,
+ final FilterHolder securityFilter) {
+ if (CollectionUtils.isNotEmpty(contentViewerWebContexts)) {
+ for (final WebAppContext contentViewerContext : contentViewerWebContexts) {
+ // add the security filter to any content viewer wars
+ if (securityFilter != null) {
+ contentViewerContext.addFilter(securityFilter, "/*", EnumSet.allOf(DispatcherType.class));
+ }
+ }
+ }
+ }
+
+ private void dumpUrls() throws SocketException {
+ final List<String> urls = new ArrayList<>();
+
+ for (Connector connector : server.getConnectors()) {
+ if (connector instanceof ServerConnector) {
+ final ServerConnector serverConnector = (ServerConnector) connector;
+
+ Set<String> hosts = new HashSet<>();
+
+ // determine the hosts
+ if (StringUtils.isNotBlank(serverConnector.getHost())) {
+ hosts.add(serverConnector.getHost());
+ } else {
+ Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ if (networkInterfaces != null) {
+ for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
+ for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
+ hosts.add(inetAddress.getHostAddress());
+ }
+ }
+ }
+ }
+
+ // ensure some hosts were found
+ if (!hosts.isEmpty()) {
+ String scheme = "http";
+ if (props.getSslPort() != null && serverConnector.getPort() == props.getSslPort()) {
+ scheme = "https";
+ }
+
+ // dump each url
+ for (String host : hosts) {
+ urls.add(String.format("%s://%s:%s", scheme, host, serverConnector.getPort()));
+ }
+ }
+ }
+ }
+
+ if (urls.isEmpty()) {
+ logger.warn("NiFi has started, but the UI is not available on any hosts. Please verify the host properties.");
+ } else {
+ // log the ui location
+ logger.info("NiFi has started. The UI is available at the following URLs:");
+ for (final String url : urls) {
+ logger.info(String.format("%s/nifi", url));
+ }
+ }
+ }
+
+ private void startUpFailure(Throwable t) {
+ System.err.println("Failed to start web server: " + t.getMessage());
+ System.err.println("Shutting down...");
+ logger.warn("Failed to start web server... shutting down.", t);
+ System.exit(1);
+ }
+
+ @Override
+ public void setExtensionMapping(ExtensionMapping extensionMapping) {
+ this.extensionMapping = extensionMapping;
+ }
+
+ @Override
+ public void setBundles(Bundle systemBundle, Set<Bundle> bundles) {
+ this.systemBundle = systemBundle;
+ this.bundles = bundles;
+ }
+
+ @Override
+ public void stop() {
+ try {
+ server.stop();
+ } catch (Exception ex) {
+ logger.warn("Failed to stop web server", ex);
+ }
+
+ try {
+ if (narAutoLoader != null) {
+ narAutoLoader.stop();
+ }
+
+ if (dcaeAutoLoader != null) {
+ dcaeAutoLoader.stop();
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to stop NAR auto-loader", e);
+ }
+ }
+
+ /**
+ * Holds the result of loading WARs for custom UIs.
+ */
+ private static class ExtensionUiInfo {
+
+ private final Collection<WebAppContext> webAppContexts;
+ private final Map<String, String> mimeMappings;
+ private final Collection<WebAppContext> componentUiExtensionWebContexts;
+ private final Collection<WebAppContext> contentViewerWebContexts;
+ private final Map<String, List<UiExtension>> componentUiExtensionsByType;
+
+ public ExtensionUiInfo(final Collection<WebAppContext> webAppContexts,
+ final Map<String, String> mimeMappings,
+ final Collection<WebAppContext> componentUiExtensionWebContexts,
+ final Collection<WebAppContext> contentViewerWebContexts,
+ final Map<String, List<UiExtension>> componentUiExtensionsByType) {
+ this.webAppContexts = webAppContexts;
+ this.mimeMappings = mimeMappings;
+ this.componentUiExtensionWebContexts = componentUiExtensionWebContexts;
+ this.contentViewerWebContexts = contentViewerWebContexts;
+ this.componentUiExtensionsByType = componentUiExtensionsByType;
+ }
+
+ public Collection<WebAppContext> getWebAppContexts() {
+ return webAppContexts;
+ }
+
+ public Map<String, String> getMimeMappings() {
+ return mimeMappings;
+ }
+
+ public Collection<WebAppContext> getComponentUiExtensionWebContexts() {
+ return componentUiExtensionWebContexts;
+ }
+
+ public Collection<WebAppContext> getContentViewerWebContexts() {
+ return contentViewerWebContexts;
+ }
+
+ public Map<String, List<UiExtension>> getComponentUiExtensionsByType() {
+ return componentUiExtensionsByType;
+ }
+ }
+}
+
+@FunctionalInterface
+interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> {
+ ServerConnector create(Server server, HttpConfiguration httpConfiguration);
+}
diff --git a/mod/designtool/designtool-web/src/main/resources/filters/canvas-min.properties b/mod/designtool/designtool-web/src/main/resources/filters/canvas-min.properties
new file mode 100644
index 0000000..1943157
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/resources/filters/canvas-min.properties
@@ -0,0 +1,19 @@
+# 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.
+
+nf.canvas.script.tags=<script type="text/javascript" src="js/nf/canvas/nf-canvas-all.js?${project.version}"></script>
+nf.canvas.style.tags=<link rel="stylesheet" href="css/nf-canvas-all.css?${project.version}" type="text/css" />\n\
+<link rel="stylesheet" href="css/message-pane.css?${project.version}" type="text/css" />\n\
+<link rel="stylesheet" href="css/nf-common-ui.css?${project.version}" type="text/css" /> \ No newline at end of file
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/pages/canvas.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/pages/canvas.jsp
new file mode 100644
index 0000000..ccfee0c
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/pages/canvas.jsp
@@ -0,0 +1,167 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>NiFi</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="shortcut icon" href="images/nifi16.ico"/>
+ <link rel="stylesheet" href="assets/reset.css/reset.css" type="text/css" />
+ ${nf.canvas.style.tags}
+ <link rel="stylesheet" href="js/codemirror/lib/codemirror.css" type="text/css" />
+ <link rel="stylesheet" href="js/codemirror/addon/hint/show-hint.css" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/nfeditor/jquery.nfeditor.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/nfeditor/languages/nfel.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/tabbs/jquery.tabbs.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/combo/jquery.combo.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/propertytable/jquery.propertytable.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/tagcloud/jquery.tagcloud.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="js/jquery/modal/jquery.modal.css?${project.version}" type="text/css" />
+ <link rel="stylesheet" href="assets/qtip2/dist/jquery.qtip.min.css?" type="text/css" />
+ <link rel="stylesheet" href="assets/jquery-ui-dist/jquery-ui.min.css" type="text/css" />
+ <link rel="stylesheet" href="assets/jquery-minicolors/jquery.minicolors.css" type="text/css" />
+ <link rel="stylesheet" href="assets/slickgrid/slick.grid.css" type="text/css" />
+ <link rel="stylesheet" href="css/slick-nifi-theme.css" type="text/css" />
+ <link rel="stylesheet" href="fonts/flowfont/flowfont.css" type="text/css" />
+ <link rel="stylesheet" href="assets/angular-material/angular-material.min.css" type="text/css" />
+ <link rel="stylesheet" href="assets/font-awesome/css/font-awesome.min.css" type="text/css" />
+ <script>
+ //force browsers to use URLSearchParams polyfill do to bugs and inconsistent browser implementations
+ URLSearchParams = undefined;
+ </script>
+ <script type="text/javascript" src="assets/url-search-params/build/url-search-params.js"></script>
+ <script type="text/javascript" src="js/codemirror/lib/codemirror-compressed.js"></script>
+ <script type="text/javascript" src="assets/d3/build/d3.min.js"></script>
+ <script type="text/javascript" src="assets/d3-selection-multi/build/d3-selection-multi.min.js"></script>
+ <script type="text/javascript" src="assets/jquery/dist/jquery.min.js"></script>
+ <script type="text/javascript" src="assets/jquery-ui-dist/jquery-ui.min.js"></script>
+ <script type="text/javascript" src="js/jquery/jquery.base64.js"></script>
+ <script type="text/javascript" src="js/jquery/jquery.center.js"></script>
+ <script type="text/javascript" src="js/jquery/jquery.ellipsis.js"></script>
+ <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
+ <script type="text/javascript" src="js/jquery/jquery.tab.js"></script>
+ <script type="text/javascript" src="assets/jquery-form/jquery.form.js"></script>
+ <script type="text/javascript" src="js/jquery/tabbs/jquery.tabbs.js?${project.version}"></script>
+ <script type="text/javascript" src="js/jquery/combo/jquery.combo.js?${project.version}"></script>
+ <script type="text/javascript" src="js/jquery/modal/jquery.modal.js?${project.version}"></script>
+ <script type="text/javascript" src="assets/jquery-minicolors/jquery.minicolors.min.js"></script>
+ <script type="text/javascript" src="assets/qtip2/dist/jquery.qtip.min.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/lib/jquery.event.drag-2.3.0.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/plugins/slick.cellrangeselector.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/plugins/slick.cellselectionmodel.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/plugins/slick.rowselectionmodel.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/plugins/slick.autotooltips.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/slick.formatters.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/slick.editors.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/slick.dataview.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/slick.core.js"></script>
+ <script type="text/javascript" src="assets/slickgrid/slick.grid.js"></script>
+ <script type="text/javascript" src="assets/angular/angular.min.js"></script>
+ <script type="text/javascript" src="assets/angular-messages/angular-messages.min.js"></script>
+ <script type="text/javascript" src="assets/angular-resource/angular-resource.min.js"></script>
+ <script type="text/javascript" src="assets/angular-route/angular-route.min.js"></script>
+ <script type="text/javascript" src="assets/angular-aria/angular-aria.min.js"></script>
+ <script type="text/javascript" src="assets/angular-animate/angular-animate.min.js"></script>
+ <script type="text/javascript" src="assets/angular-material/angular-material.min.js"></script>
+ <script type="text/javascript" src="assets/JSON2/json2.js"></script>
+ <script type="text/javascript" src="js/nf/nf-namespace.js?${project.version}"></script>
+ <script type="text/javascript" src="js/nf/nf-ng-namespace.js?${project.version}"></script>
+ <script type="text/javascript" src="js/nf/canvas/nf-ng-canvas-namespace.js?${project.version}"></script>
+ ${nf.canvas.script.tags}
+ <script type="text/javascript" src="js/jquery/nfeditor/languages/nfel.js?${project.version}"></script>
+ <script type="text/javascript" src="js/jquery/nfeditor/jquery.nfeditor.js?${project.version}"></script>
+ <script type="text/javascript" src="js/jquery/propertytable/jquery.propertytable.js?${project.version}"></script>
+ <script type="text/javascript" src="js/jquery/tagcloud/jquery.tagcloud.js?${project.version}"></script>
+
+ <script type="text/javascript" src="js/jquery/dcae-mod.js"></script>
+
+ </head>
+ <body ng-controller="ngCanvasAppCtrl" id="canvas-body">
+ <div id="splash">
+ <div id="splash-img" layout="row" layout-align="center center">
+ <md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="150"></md-progress-circular>
+ </div>
+ </div>
+ <jsp:include page="/WEB-INF/partials/message-pane.jsp"/>
+ <jsp:include page="/WEB-INF/partials/banners-main.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/canvas-header.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/flow-status.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/about-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/ok-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/yes-no-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/status-history-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/search-users-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/disable-controller-service-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/enable-controller-service-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-controller-service-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-reporting-task-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-processor-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-port-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-process-group-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-remote-process-group-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/new-template-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/upload-template-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/instantiate-template-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/fill-color-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/connections-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/save-flow-version-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/import-flow-version-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/revert-local-changes-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/show-local-changes-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/registry-configuration-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/distribution-environment-dialog.jsp"/>
+ <div id="canvas-container" class="unselectable"></div>
+ <div id="canvas-tooltips">
+ <div id="processor-tooltips"></div>
+ <div id="port-tooltips"></div>
+ <div id="process-group-tooltips"></div>
+ <div id="remote-process-group-tooltips"></div>
+ </div>
+ <jsp:include page="/WEB-INF/partials/canvas/navigation.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/settings-content.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/shell.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/controller-service-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/reporting-task-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/processor-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/processor-details.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/variable-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/process-group-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/override-policy-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/policy-management.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/remote-process-group-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/remote-process-group-details.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/remote-process-group-ports.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/remote-port-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/port-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/port-details.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/label-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/connection-configuration.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/drop-request-status-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/flowfile-details-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/listing-request-status-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/queue-listing.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/component-state-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/canvas/component-version-dialog.jsp"/>
+ <jsp:include page="/WEB-INF/partials/connection-details.jsp"/>
+ <div id="context-menu" class="context-menu unselectable"></div>
+ <span id="nifi-content-viewer-url" class="hidden"></span>
+ </body>
+</html>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
new file mode 100644
index 0000000..3afbe66
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
@@ -0,0 +1,191 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<md-toolbar id="header" layout-align="space-between center" layout="row" class="md-small md-accent md-hue-1">
+ <img id="nifi-logo" src="images/dcae-logo.png">
+ <div flex layout="row" layout-align="space-between center">
+ <div id="component-container">
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.processor}}"
+ id="processor-component"
+ class="component-button icon icon-processor"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.processorComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.inputPort}}"
+ id="port-in-component"
+ class="component-button icon icon-port-in"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.inputPortComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.outputPort}}"
+ id="port-out-component"
+ class="component-button icon icon-port-out"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.outputPortComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.processGroup}}"
+ id="group-component"
+ class="component-button icon icon-group"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.groupComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.remoteProcessGroup}}"
+ id="group-remote-component"
+ class="component-button icon icon-group-remote"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.remoteGroupComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.funnel}}"
+ id="funnel-component"
+ class="component-button icon icon-funnel"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.funnelComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.template}}"
+ id="template-component"
+ class="component-button icon icon-template"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.templateComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ <button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.label}}"
+ id="label-component"
+ class="component-button icon icon-label"
+ ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
+ nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.labelComponent);">
+ <span class="component-button-grip"></span>
+ </button>
+ </div>
+ <div layout="row" layout-align="space-between center">
+ <div layout-align="space-between end" layout="column">
+ <div layout="row" layout-align="space-between center" id="current-user-container">
+ <span id="anonymous-user-alert" class="hidden fa fa-warning"></span>
+ <div></div>
+ <div id="current-user"></div>
+ </div>
+ <div id="login-link-container">
+ <span id="login-link" class="link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.loginCtrl.shell.launch();">log in</span>
+ </div>
+ <div id="logout-link-container" style="display: none;">
+ <span id="logout-link" class="link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.logoutCtrl.logout();">log out</span>
+ </div>
+ </div>
+ <md-menu md-position-mode="target-right target" md-offset="-1 44">
+ <button md-menu-origin id="global-menu-button" ng-click="$mdMenu.open()">
+ <div class="fa fa-navicon"></div>
+ </button>
+ <md-menu-content id="global-menu-content">
+ <md-menu-item layout-align="space-around center">
+ <a id="reporting-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.summary.shell.launch();">
+ <i class="fa fa-table"></i>Summary
+ </a>
+ </md-menu-item>
+ <md-menu-item layout-align="space-around center">
+ <a id="counters-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.counters.shell.launch();"
+ ng-class="{disabled: !appCtrl.nf.Common.canAccessCounters()}">
+ <i class="icon icon-counter"></i>Counters
+ </a>
+ </md-menu-item>
+ <md-menu-item layout-align="space-around center">
+ <a id="bulletin-board-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.bulletinBoard.shell.launch();">
+ <i class="fa fa-sticky-note-o"></i>Bulletin Board
+ </a>
+ </md-menu-item>
+ <md-menu-divider></md-menu-divider>
+ <md-menu-item
+ layout-align="space-around center">
+ <a id="provenance-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.dataProvenance.shell.launch();"
+ ng-class="{disabled: !appCtrl.nf.Common.canAccessProvenance()}">
+ <i class="icon icon-provenance"></i>Data Provenance
+ </a>
+ </md-menu-item>
+ <md-menu-divider></md-menu-divider>
+ <md-menu-item layout-align="space-around center">
+ <a id="flow-settings-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.controllerSettings.shell.launch();">
+ <i class="fa fa-wrench"></i>Controller Settings
+ </a>
+ </md-menu-item>
+ <md-menu-item ng-if="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.cluster.visible();"
+ layout-align="space-around center">
+ <a id="cluster-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.cluster.shell.launch();"
+ ng-class="{disabled: !appCtrl.nf.Common.canAccessController()}">
+ <i class="fa fa-cubes"></i>Cluster
+ </a>
+ </md-menu-item>
+ <md-menu-item layout-align="space-around center">
+ <a id="history-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.flowConfigHistory.shell.launch();">
+ <i class="fa fa-history"></i>Flow Configuration History
+ </a>
+ </md-menu-item>
+ <md-menu-divider ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()"></md-menu-divider>
+ <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">
+ <a id="users-link" layout="row"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.users.shell.launch();"
+ ng-class="{disabled: !(appCtrl.nf.Common.canAccessTenants())}">
+ <i class="fa fa-users"></i>Users
+ </a>
+ </md-menu-item>
+ <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">
+ <a id="policies-link" layout="row"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.policies.shell.launch();"
+ ng-class="{disabled: !(appCtrl.nf.Common.canAccessTenants() && appCtrl.nf.Common.canModifyPolicies())}">
+ <i class="fa fa-key"></i>Policies
+ </a>
+ </md-menu-item>
+ <md-menu-divider></md-menu-divider>
+ <md-menu-item layout-align="space-around center">
+ <a id="templates-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.templates.shell.launch();">
+ <i class="icon icon-template"></i>Templates
+ </a>
+ </md-menu-item>
+ <md-menu-divider></md-menu-divider>
+ <md-menu-item layout-align="space-around center">
+ <a id="help-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.help.shell.launch();">
+ <i class="fa fa-question-circle"></i>Help
+ </a>
+ </md-menu-item>
+ <md-menu-item layout-align="space-around center">
+ <a id="about-link"
+ ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.about.modal.show();">
+ <i class="fa fa-info-circle"></i>About
+ </a>
+ </md-menu-item>
+ </md-menu-content>
+ </md-menu>
+ </div>
+ </div>
+</md-toolbar>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
new file mode 100644
index 0000000..c0e368c
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
@@ -0,0 +1,218 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="connection-configuration" layout="column" class="hidden large-dialog">
+ <div class="connection-configuration-tab-container dialog-content">
+ <div id="connection-configuration-tabs" class="tab-container"></div>
+ <div id="connection-configuration-tabs-content">
+ <div id="connection-settings-tab-content" class="configuration-tab">
+ <div class="settings-left">
+ <div class="setting">
+ <div class="setting-name">Name</div>
+ <div class="setting-field">
+ <input type="text" id="connection-name" name="connection-name" class="setting-input"/>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">Id</div>
+ <div class="setting-field">
+ <span type="text" id="connection-id"></span>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">
+ FlowFile expiration
+ <div class="fa fa-question-circle" alt="Info" title="The maximum amount of time an object may be in the flow before it will be automatically aged out of the flow."></div>
+ </div>
+ <div class="setting-field">
+ <input type="text" id="flow-file-expiration" name="flow-file-expiration" class="setting-input"/>
+ </div>
+ </div>
+ <div class="multi-column-settings">
+ <div class="setting">
+ <div class="setting-name">
+ Back Pressure<br/>Object threshold
+ <div class="fa fa-question-circle" alt="Info" title="The maximum number of objects that can be queued before back pressure is applied."></div>
+ </div>
+ <div class="setting-field">
+ <input type="text" id="back-pressure-object-threshold" name="back-pressure-object-threshold" class="setting-input"/>
+ </div>
+ </div>
+ <div class="separator">&nbsp;</div>
+ <div class="setting">
+ <div class="setting-name">
+ &nbsp;<br/>Size threshold
+ <div class="fa fa-question-circle" alt="Info" title="The maximum data size of objects that can be queued before back pressure is applied."></div>
+ </div>
+ <div class="setting-field">
+ <input type="text" id="back-pressure-data-size-threshold" name="back-pressure-data-size-threshold" class="setting-input"/>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="multi-column-settings">
+ <div class="setting">
+ <div class="setting-name">
+ Load Balance Strategy
+ <div class="fa fa-question-circle" alt="Info" title="How to load balance the data in this Connection across the nodes in the cluster."></div>
+ </div>
+ <div class="setting-field">
+ <div id="load-balance-strategy-combo"></div>
+ </div>
+ </div>
+ <div id="load-balance-partition-attribute-setting-separator" class="separator">&nbsp;</div>
+ <div id="load-balance-partition-attribute-setting" class="setting">
+ <div class="setting-name">
+ Attribute Name
+ <div class="fa fa-question-circle" alt="Info" title="The FlowFile Attribute to use for determining which node a FlowFile will go to."></div>
+ </div>
+ <div class="setting-field">
+ <input type="text" id="load-balance-partition-attribute" name="load-balance-partition-attribute" class="setting-input"/>
+ </div>
+ </div>
+ </div>
+ <div id="load-balance-compression-setting" class="setting">
+ <div class="setting-name">
+ Load Balance Compression
+ <div class="fa fa-question-circle" alt="Info" title="Whether or not data should be compressed when being transferred between nodes in the cluster."></div>
+ </div>
+ <div class="setting-field">
+ <div id="load-balance-compression-combo"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="spacer">&nbsp;</div>
+ <div class="settings-right">
+ <div class="setting">
+ <div class="setting-name">
+ Available prioritizers
+ <div class="fa fa-question-circle" alt="Info" title="Available prioritizers that could reprioritize FlowFiles in this work queue."></div>
+ </div>
+ <div class="setting-field">
+ <ul id="prioritizer-available"></ul>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">
+ Selected prioritizers
+ <div class="fa fa-question-circle" alt="Info" title="Prioritizers that have been selected to reprioritize FlowFiles in this work queue."></div>
+ </div>
+ <div class="setting-field">
+ <ul id="prioritizer-selected"></ul>
+ </div>
+ </div>
+ </div>
+ <input type="hidden" id="connection-uri" name="connection-uri"/>
+ <input type="hidden" id="connection-source-component-id" name="connection-source-component-id"/>
+ <input type="hidden" id="connection-source-id" name="connection-source-id"/>
+ <input type="hidden" id="connection-source-group-id" name="connection-source-group-id"/>
+ <input type="hidden" id="connection-destination-component-id" name="connection-destination-component-id"/>
+ <input type="hidden" id="connection-destination-id" name="connection-destination-id"/>
+ <input type="hidden" id="connection-destination-group-id" name="connection-destination-group-id"/>
+ </div>
+ <div id="connection-details-tab-content" class="configuration-tab">
+ <div class="settings-left">
+ <div id="read-only-output-port-source" class="setting hidden">
+ <div class="setting-name">From output</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="read-only-output-port-name" class="ellipsis"></div>
+ </div>
+ </div>
+ <div id="output-port-source" class="setting hidden">
+ <div class="setting-name">From output</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="output-port-options"></div>
+ </div>
+ </div>
+ <div id="input-port-source" class="setting hidden">
+ <div class="setting-name">From input</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="input-port-source-name" class="label ellipsis"></div>
+ </div>
+ </div>
+ <div id="funnel-source" class="setting hidden">
+ <div class="setting-name">From funnel</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="funnel-source-name" class="label ellipsis" title="funnel">funnel</div>
+ </div>
+ </div>
+ <div id="processor-source" class="setting hidden">
+ <div class="setting-name">From processor</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="processor-source-details">
+ <div id="processor-source-name" class="label ellipsis"></div>
+ <div id="processor-source-type" class="ellipsis"></div>
+ </div>
+ </div>
+ </div>
+ <div id="connection-source-group" class="setting">
+ <div class="setting-name">Within group</div>
+ <div class="setting-field">
+ <div id="connection-source-group-name"></div>
+ </div>
+ </div>
+ </div>
+ <div class="spacer">&nbsp;</div>
+ <div class="settings-right">
+ <div id="input-port-destination" class="setting hidden">
+ <div class="setting-name">To input</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="input-port-options"></div>
+ </div>
+ </div>
+ <div id="output-port-destination" class="setting hidden">
+ <div class="setting-name">To output</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="output-port-destination-name" class="label ellipsis"></div>
+ </div>
+ </div>
+ <div id="funnel-destination" class="setting hidden">
+ <div class="setting-name">To funnel</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="funnel-destination-name" class="label ellipsis" title="funnel">funnel</div>
+ </div>
+ </div>
+ <div id="processor-destination" class="setting hidden">
+ <div class="setting-name">To processor</div>
+ <div class="setting-field connection-terminal-label">
+ <div id="processor-destination-details">
+ <div id="processor-destination-name" class="label ellipsis"></div>
+ <div id="processor-destination-type" class="ellipsis"></div>
+ </div>
+ </div>
+ </div>
+ <div id="connection-destination-group" class="setting">
+ <div class="setting-name">Within group</div>
+ <div class="setting-field">
+ <div id="connection-destination-group-name"></div>
+ </div>
+ </div>
+ </div>
+ <div id="relationship-names-container" class="hidden">
+ <div class="setting-name">Data relationships</div>
+ <div class="setting-field">
+ <div id="relationship-names"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/distribution-environment-dialog.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/distribution-environment-dialog.jsp
new file mode 100644
index 0000000..3d7e8d9
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/distribution-environment-dialog.jsp
@@ -0,0 +1,42 @@
+<%--
+================================================================================
+Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+================================================================================
+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.
+============LICENSE_END=========================================================
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="distribution-environment-dialog" layout="column" class="hidden medium-dialog">
+ <div class="dialog-content">
+ <div class="setting">
+ <div class="setting-name">Name</div>
+ <div class="setting-field">
+ <span id="distribution-environment-id" class="hidden"></span>
+ <input type="text" id="distribution-environment-name" class="setting-input"/>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">Runtime API URL</div>
+ <div class="setting-field">
+ <input type="text" id="distribution-environment-location" class="setting-input" placeholder="http://runtime-host:port"/>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">Description</div>
+ <div class="setting-field">
+ <textarea id="distribution-environment-description" class="setting-input"></textarea>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
new file mode 100644
index 0000000..2efe0a1
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
@@ -0,0 +1,33 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="flow-status" flex layout="row" layout-align="space-between center">
+ <div id="flow-status-container" layout="row" layout-align="space-around center">
+ </div>
+
+ <div layout="row" layout-align="end center">
+ <div id="search-container">
+ <button id="search-button" ng-click="appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.search.toggleSearchField();"><i class="fa fa-search"></i></button>
+ <input id="search-field" type="text" placeholder="Search"/>
+ </div>
+ <button id="bulletin-button"><i class="fa fa-sticky-note-o"></i></button>
+ </div>
+ </div>
+<div id="search-flow-results"></div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
new file mode 100644
index 0000000..caf7278
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
@@ -0,0 +1,129 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<nf-breadcrumbs
+ breadcrumbs="appCtrl.serviceProvider.breadcrumbsCtrl.getBreadcrumbs();"
+ click-func="appCtrl.nf.CanvasUtils.getComponentByType('ProcessGroup').enterGroup"
+ highlight-crumb-id="appCtrl.nf.CanvasUtils.getGroupId();"
+ separator-func="appCtrl.nf.Common.isDefinedAndNotNull"
+ is-tracking="appCtrl.serviceProvider.breadcrumbsCtrl.isTracking"
+ get-version-control-class="appCtrl.serviceProvider.breadcrumbsCtrl.getVersionControlClass"
+ get-version-control-tooltip="appCtrl.serviceProvider.breadcrumbsCtrl.getVersionControlTooltip">
+</nf-breadcrumbs>
+<div id="graph-controls">
+ <div id="navigation-control" class="graph-control">
+ <div class="graph-control-docked pointer fa fa-compass" title="Navigate"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.undock($event)">
+ </div>
+ <div class="graph-control-header-container hidden pointer"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.expand($event)">
+ <div class="graph-control-header-icon fa fa-compass">
+ </div>
+ <div class="graph-control-header">Navigate</div>
+ <div class="graph-control-header-action">
+ <div class="graph-control-expansion fa fa-plus-square-o pointer"></div>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div class="graph-control-content hidden">
+ <div id="navigation-buttons">
+ <div id="naviagte-zoom-in" class="action-button" title="Zoom In"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.zoomIn();">
+ <button><div class="graph-control-action-icon fa fa-search-plus"></div></button>
+ </div>
+ <div class="button-spacer-small">&nbsp;</div>
+ <div id="naviagte-zoom-out" class="action-button" title="Zoom Out"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.zoomOut();">
+ <button><div class="graph-control-action-icon fa fa-search-minus"></div></button>
+ </div>
+ <div class="button-spacer-large">&nbsp;</div>
+ <div id="naviagte-zoom-fit" class="action-button" title="Fit"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.zoomFit();">
+ <button><div class="graph-control-action-icon icon icon-zoom-fit"></div></button>
+ </div>
+ <div class="button-spacer-small">&nbsp;</div>
+ <div id="naviagte-zoom-actual-size" class="action-button" title="Actual"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.zoomActualSize();">
+ <button><div class="graph-control-action-icon icon icon-zoom-actual"></div></button>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div id="birdseye"></div>
+ </div>
+ </div>
+ <div id="operation-control" class="graph-control">
+ <div class="graph-control-docked pointer fa fa-hand-o-up" title="Operate"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.undock($event)">
+ </div>
+ <div class="graph-control-header-container hidden pointer"
+ ng-click="appCtrl.serviceProvider.graphControlsCtrl.expand($event)">
+ <div class="graph-control-header-icon fa fa-hand-o-up">
+ </div>
+ <div class="graph-control-header">Operate</div>
+ <div class="graph-control-header-action">
+ <div class="graph-control-expansion fa fa-plus-square-o pointer"></div>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div class="graph-control-content hidden">
+ <div id="operation-context">
+ <div id="operation-context-logo">
+ <i class="icon" ng-class="appCtrl.serviceProvider.graphControlsCtrl.getContextIcon()"></i>
+ </div>
+ <div id="operation-context-details-container">
+ <div id="operation-context-name"><strong> {{appCtrl.serviceProvider.graphControlsCtrl.getContextName()}} </strong></div>
+ <div id="operation-context-type" ng-class="appCtrl.serviceProvider.graphControlsCtrl.hide()">{{appCtrl.serviceProvider.graphControlsCtrl.getContextType()}}</div>
+ </div>
+ <div class="clear"></div>
+ <div id="operation-context-id" ng-class="appCtrl.serviceProvider.graphControlsCtrl.hide()">{{appCtrl.serviceProvider.graphControlsCtrl.getContextId()}}</div>
+ </div> <div id="operation-buttons">
+ <div>
+
+ <div id="operation-context-type">Distribute for deployment:</div>
+ <br>
+ <div>
+ <select name="environment" id="environmentType" class="combo" onchange="onEnvironmentSelect()">
+ <option class="combo-option-text" disabled selected>Select Environment</option>
+ </select>
+ </div>
+
+ <br>
+ <div class="button-spacer-large">&nbsp;</div>
+ <div id="operate-refresh" class="action-button" title="Refresh Environments">
+ <button id="refresh-env-btn" onclick="refreshEnvironments()" >
+ <div class="graph-control-action-icon fa fa-refresh"></div><span></span></button>
+ </div>
+ <div class="button-spacer-large">&nbsp;</div>
+ <div id="operate-delete" class="action-button" title="Delete">
+ <button ng-click="appCtrl.nf.Actions['delete'](appCtrl.nf.CanvasUtils.getSelection());"
+ ng-disabled="!appCtrl.nf.CanvasUtils.areDeletable(appCtrl.nf.CanvasUtils.getSelection());">
+ <div class="graph-control-action-icon fa fa-trash"></div><span></span></button>
+ </div>
+ <div class="button-spacer-large">&nbsp;</div>
+ <div id="operate-submit" class="action-button" title="Submit">
+ <button id="operate-submit-btn" onclick="distributeGraph()" >
+ <div class="graph-control-action-icon fa fa-check"></div><span></span></button>
+ </div>
+ <div class="clear"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp
new file mode 100644
index 0000000..b540ff7
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp
@@ -0,0 +1,86 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="settings" class="hidden">
+ <div id="settings-header-text" class="settings-header-text">NiFi Settings</div>
+ <div class="settings-container">
+ <div>
+ <div id="settings-tabs" class="settings-tabs tab-container"></div>
+ <div class="clear"></div>
+ </div>
+ <div id="settings-tabs-content">
+ <button id="new-service-or-task" class="add-button fa fa-plus" title="Create a new reporting task controller service" style="display: block;"></button>
+ <div id="general-settings-tab-content" class="configuration-tab">
+ <div id="general-settings">
+ <div class="setting">
+ <div class="setting-name">
+ Maximum timer driven thread count
+ <div class="fa fa-question-circle" alt="Info" title="The maximum number of threads for timer driven processors available to the system."></div>
+ </div>
+ <div class="editable setting-field">
+ <input type="text" id="maximum-timer-driven-thread-count-field" class="setting-input"/>
+ </div>
+ <div class="read-only setting-field">
+ <span id="read-only-maximum-timer-driven-thread-count-field"></span>
+ </div>
+ </div>
+ <div class="setting">
+ <div class="setting-name">
+ Maximum event driven thread count
+ <div class="fa fa-question-circle" alt="Info" title="The maximum number of threads for event driven processors available to the system."></div>
+ </div>
+ <div class="editable setting-field">
+ <input type="text" id="maximum-event-driven-thread-count-field" class="setting-input"/>
+ </div>
+ <div class="read-only setting-field">
+ <span id="read-only-maximum-event-driven-thread-count-field"></span>
+ </div>
+ </div>
+ <div class="editable settings-buttons">
+ <div id="settings-save" class="button">Apply</div>
+ <div class="clear"></div>
+ </div>
+ </div>
+ </div>
+ <div id="controller-services-tab-content" class="configuration-tab controller-settings-table">
+ <div id="controller-services-table" class="settings-table"></div>
+ </div>
+ <div id="reporting-tasks-tab-content" class="configuration-tab controller-settings-table">
+ <div id="reporting-tasks-table" class="settings-table"></div>
+ </div>
+ <div id="registries-tab-content" class="configuration-tab controller-settings-table">
+ <div id="registries-table" class="settings-table"></div>
+ </div>
+ <div id="distribution-environment-content" class="configuration-tab controller-settings-table">
+ <div id="distribution-environments-table" class="settings-table" style="width:100%; margin:20px;"></div>
+ </div>
+
+ </div>
+ </div>
+ <div id="settings-refresh-container">
+ <button id="settings-refresh-button" class="refresh-button pointer fa fa-refresh" title="Refresh"></button>
+ <div id="settings-last-refreshed-container" class="last-refreshed-container">
+ Last updated:&nbsp;<span id="settings-last-refreshed" class="value-color"></span>
+ </div>
+ <div id="settings-loading-container" class="loading-container"></div>
+ <div id="controller-cs-availability" class="hidden">Listed services are available to all Reporting Tasks and services defined in the Controller Settings.</div>
+ <div class="clear"></div>
+ </div>
+</div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/shell.jsp b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/shell.jsp
new file mode 100644
index 0000000..5b6c4c3
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/WEB-INF/partials/canvas/shell.jsp
@@ -0,0 +1,34 @@
+<%--
+ 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.
+
+ Modifications to the original nifi code for the ONAP project are made
+ available under the Apache License, Version 2.0
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="shell-dialog" class="hidden cancellable">
+ <div id="shell-container" class="dialog-content">
+ <div id="shell-close-container">
+ <button id="shell-undock-button" class="undock-normal pointer " title="Open in New Window">
+ <div class="fa fa-external-link-square"></div>
+ </button>
+ <button id="shell-close-button" class="close-normal pointer" onclick="onCloseSettings()" title="Close">
+ <div class="fa fa-times"></div>
+ </button>
+ <div class="clear"></div>
+ </div>
+ <div id="shell"></div>
+ </div>
+</div>
diff --git a/mod/designtool/designtool-web/src/main/webapp/css/navigation.css b/mod/designtool/designtool-web/src/main/webapp/css/navigation.css
new file mode 100644
index 0000000..05adc45
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/css/navigation.css
@@ -0,0 +1,338 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* general graph control styles */
+
+#graph-controls {
+ position: absolute;
+ left: 0;
+ top: 110px;
+ z-index: 2;
+}
+
+#graph-controls .icon {
+ font-size: 18px;
+ line-height: 23px;
+ margin-left: -2px;
+}
+
+#graph-controls .fa {
+ font-size: 18px;
+ margin-left: -2px;
+}
+
+.graph-control-header-icon.fa {
+ color: #004849; /*link-color*/
+ margin-left: 7px !important;
+}
+
+div.graph-control {
+ box-shadow: 0 1px 6px rgba(0,0,0,0.25);
+ background-color: rgba(249, 250, 251, 0.9);
+ border-top: 1px solid #aabbc3;
+ border-right: 1px solid #aabbc3;
+ border-bottom: 1px solid #aabbc3;
+ margin-bottom: 2px;
+}
+
+.graph-control-content {
+ margin-left: 10px;
+ margin-right: 10px;
+ margin-bottom: 10px;
+}
+
+.docked {
+ height: 32px;
+ width: 32px;
+}
+
+div.graph-control-docked {
+ height: 100%;
+ width: 100%;
+ text-align: center;
+ line-height: 34px;
+ color: #004849;
+}
+
+.docked:hover {
+ border-top: 1px solid #004849; /*tint base-color 60%*/
+ border-right: 1px solid #004849; /*tint base-color 60%*/
+ border-bottom: 1px solid #004849; /*tint base-color 60%*/
+}
+
+div.graph-control button {
+ line-height: 30px;
+ border: 1px solid #CCDADB; /*tint link-color 80%*/
+ background-color: rgba(249,250,251,1);
+ color: #004849;
+}
+
+div.graph-control button:hover {
+ border: 1px solid #004849; /*link-color*/
+}
+
+div.graph-control button:disabled {
+ color: #CCDADB; /*tint link-color 80%*/
+ cursor: not-allowed;
+ border: 1px solid #CCDADB; /*tint link-color 80%*/
+}
+
+div.graph-control div.graph-control-expansion {
+ color: #728E9B;
+ line-height: 34px;
+ margin-left: 9px !important;
+}
+
+div.graph-control-header-icon {
+ float: left;
+ margin: 8px 10px 0px 0px;
+}
+
+div.graph-control-header {
+ float: left;
+ font-size: 12px;
+ font-family: 'Roboto Slab';
+ color: #262626;
+ letter-spacing: 0.05rem;
+ margin: 10px 0px;
+}
+
+div.graph-control-header-action {
+ float: right;
+ height: 32px;
+ width: 32px;
+}
+
+.graph-control-header-container:hover {
+ background: linear-gradient(90deg, rgba(227,232,235,0) 254px, rgba(227,232,235,1) 32px);
+}
+
+/* navigate buttons */
+
+#navigation-buttons {
+ margin-bottom: 5px;
+ margin-top: 10px;
+}
+
+#operation-context {
+ margin-top: 10px;
+}
+
+#operation-context-logo {
+ float: left;
+}
+
+#operation-context-logo i.icon {
+ font-size: 32px;
+ font-family: flowfont;
+ color: #ad9897;
+}
+
+#operation-context-details-container {
+ float: left;
+ padding-left: 10px;
+}
+
+#operation-context-name {
+ height: 15px;
+ font-size: 15px;
+ font-family: Roboto;
+ color: #262626;
+ width: 230px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+#operation-context-type {
+ font-size: 12px;
+ font-family: Roboto;
+ color: #728e9b;
+ margin-top: 3px;
+}
+
+#operation-context-id {
+ font-size: 12px;
+ font-family: Roboto;
+ color: #775351;
+ margin-top: 10px;
+}
+
+#operation-context-type.invisible, #operation-context-id.invisible {
+ visibility: hidden;
+}
+
+#operation-buttons {
+ margin-top: 10px;
+}
+
+div.action-button {
+ float: left;
+}
+
+
+#operate-delete button {
+ width: inherit;
+ padding: 0 7px;
+}
+
+#operate-delete button span{
+ padding-left: 5px;
+ font-size: 12px;
+ }
+
+#operate-submit button {
+ width: inherit;
+ padding: 0 7px;
+}
+
+ #operate-submit button span{
+ padding-left: 5px;
+ font-size: 12px;
+ color: green;
+ }
+
+#operate-refresh button {
+ width: inherit;
+ padding: 0 7px;
+}
+
+ #operate-refresh button span{
+ padding-left: 5px;
+ font-size: 12px;
+ color: blue;
+ }
+
+div.graph-control div.icon-disabled {
+ color: #ddd;
+}
+
+div.button-spacer-small {
+ float: left;
+ width: 2px;
+}
+
+div.button-spacer-large {
+ float: left;
+ width: 12px;
+}
+
+/* outline/birdseye */
+
+#birdseye svg, #birdseye canvas {
+ position: absolute;
+ overflow: hidden;
+}
+
+#birdseye {
+ width: 264px;
+ height: 150px;
+ background: #fff;
+ z-index: 1001;
+ overflow: hidden;
+ border: 1px solid #e5ebed;
+}
+
+.brush .selection {
+ stroke: #666;
+ fill-opacity: .125;
+ shape-rendering: crispEdges;
+}
+
+rect.birdseye-brush {
+ stroke: #7098ad;
+ fill: transparent;
+}
+
+/* styles for the breadcrumbs bar */
+
+#breadcrumbs {
+ position: absolute;
+ bottom: 0px;
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
+ background-color: rgba(249, 250, 251, 0.9);
+ border-top: 1px solid #aabbc3;
+ color: #598599;
+ z-index: 3;
+ height: 31px;
+ width: 100%;
+}
+
+#cluster-indicator {
+ width: 49px;
+ height: 15px;
+ background-color: transparent;
+ display: none;
+ position: absolute;
+ left: 59px;
+ top: 8px;
+}
+
+span.breadcrumb-version-control-green {
+ color: #1a9964;
+}
+
+span.breadcrumb-version-control-red {
+ color: #ba554a;
+}
+
+span.breadcrumb-version-control-gray {
+ color: #666666;
+}
+
+#breadcrumbs-left-border {
+ position: absolute;
+ left: 0;
+ width: 10px;
+ height: 14px;
+ z-index: 3;
+ background-color: transparent;
+ background: linear-gradient(to right, rgba(249, 250, 251, 0.97), rgba(255, 255, 255, 0));
+ filter: progid:DXImageTransform.Microsoft.gradient(gradientType=1, startColorstr=#ffffffff, endColorstr=#00ffffff);
+}
+
+#breadcrumbs-right-border {
+ position: absolute;
+ right: 0px;
+ width: 10px;
+ height: 14px;
+ z-index: 3;
+ background-color: transparent;
+ background: linear-gradient(to left, rgba(249, 250, 251, 0.97), rgba(255, 255, 255, 0));
+ filter: progid:DXImageTransform.Microsoft.gradient(gradientType=1, startColorstr=#00ffffff, endColorstr=#ffffffff);
+}
+
+#data-flow-title-viewport {
+ overflow: hidden;
+ position: absolute;
+ left: 5px;
+ top: 8px;
+ right: 5px;
+ z-index: 4;
+}
+
+#data-flow-title-container {
+ font-size: 13px;
+ color: #000;
+ position: relative;
+ float: left;
+ white-space: nowrap;
+ line-height: normal;
+}
diff --git a/mod/designtool/designtool-web/src/main/webapp/images/dcae-logo.png b/mod/designtool/designtool-web/src/main/webapp/images/dcae-logo.png
new file mode 100644
index 0000000..2e0d5a0
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/images/dcae-logo.png
Binary files differ
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/jquery/dcae-mod.js b/mod/designtool/designtool-web/src/main/webapp/js/jquery/dcae-mod.js
new file mode 100644
index 0000000..879739c
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/jquery/dcae-mod.js
@@ -0,0 +1,135 @@
+/*
+============LICENSE_START=======================================================
+Copyright (c) 2019 AT&T Intellectual Property. All rights reserved.
+================================================================================
+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.
+============LICENSE_END=========================================================
+*/
+
+console.log("loading dcae-mod");
+
+ var dt_id;
+ var hostname;
+
+ /**
+ * @desc: on load of page, makes submit button disabled. Also makes an api call to get the host IP of the current instance
+ */
+ $(document).ready(function (){
+ if(dt_id == null){ $('#operate-submit-btn').prop('disabled', true); }
+
+ //get hostname
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/config',
+ dataType: 'json',
+ contentType: 'application/json',
+ success: function(data){
+ hostname= data.flowConfiguration.dcaeDistributorApiHostname;
+
+ //function call: invokes api to refresh the list of Envs
+ if(hostname){ getDistributionTargets(); }
+ }
+ });
+ });
+
+ /**
+ * common function to reuse : invokes api to get new updates list environments.
+ * @desc: Makes the select dropdown empty first. Then manually add Placeholder as first/default Option.
+ * And then dynamically add list of Environments as Options.
+ */
+ function getDistributionTargets(){
+ var select = document.getElementById("environmentType");
+ if(select && select.options && select.options.length > 0){
+ select.options.length=0;
+ var element= document.createElement("option");
+ element.textContent= "Select Environment";
+ element.selected=true;
+ element.disabled=true;
+ element.className="combo-option-text";
+ select.appendChild(element);
+ }else{ select=[]; }
+
+ $.ajax({
+ type: 'GET',
+ url: hostname+'/distribution-targets',
+ dataType: 'json',
+ contentType: 'application/json',
+ success: function(data){
+ if(data){
+ for(var i=0; i < data.distributionTargets.length; i++){
+ var opt= data.distributionTargets[i];
+ var element= document.createElement("option");
+ element.textContent= opt.name;
+ element.value= opt.id;
+ element.className="combo-option-text";
+ select.appendChild(element);
+ }
+ }
+ }
+ })
+ }
+
+ /**
+ * @desc: submit button functionality to distribute/POST process group to the environment.
+ */
+ var distributeGraph = function(){
+ var selected_id = $('#operation-context-id').text();
+ // process group id (nifi api) != flow id (nifi registry api)
+ // so must first fetch the flow id from nifi api
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/process-groups/'+selected_id,
+ contentType: 'application/json',
+ success: function(data) {
+ const flow_id = data["component"]["versionControlInformation"]["flowId"];
+ const request = {"processGroupId": flow_id}
+
+ $.ajax({
+ type: 'POST',
+ data: JSON.stringify(request),
+ url: hostname+'/distribution-targets/'+dt_id+'/process-groups',
+ dataType: 'json',
+ contentType: 'application/json',
+ success: function(data){
+ alert("Success, Your flow have been distributed successfully");
+ },
+ error: function(err) {
+ alert("Issue with distribution:\n\n" + JSON.stringify(err, null, 2));
+ }
+ });
+ }
+ })
+ };
+
+
+ /**
+ * @desc: selection of distribution target environment to post the process group
+ */
+ var onEnvironmentSelect = function(){
+ dt_id = $('#environmentType').val();
+ console.log(dt_id);
+ if(dt_id == null){ $('#operate-submit-btn').prop('disabled', true); }
+ else{ $('#operate-submit-btn').prop('disabled', false); }
+ };
+
+
+ /**
+ * @desc: event handler for Refresh icon in Operate panel : invokes api to refresh the list of Envs
+ */
+ var refreshEnvironments= function(){ getDistributionTargets(); };
+
+
+ /**
+ * @desc: event handler for Close icon of Setting/ Distribution Env CRUD dialog : invokes api to refresh the list of Envs
+ */
+ var onCloseSettings= function(){ getDistributionTargets(); };
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js
new file mode 100644
index 0000000..0005837
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js
@@ -0,0 +1,288 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'nf.Common'],
+ function ($, nfCommon) {
+ return (nf.ng.BreadcrumbsCtrl = factory($, nfCommon));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.ng.BreadcrumbsCtrl =
+ factory(require('jquery'),
+ require('nf.Common')));
+ } else {
+ nf.ng.BreadcrumbsCtrl = factory(root.$,
+ root.nf.Common);
+ }
+}(this, function ($, nfCommon) {
+ 'use strict';
+
+ return function (serviceProvider) {
+ 'use strict';
+
+ function BreadcrumbsCtrl() {
+ this.breadcrumbs = [];
+ }
+
+ BreadcrumbsCtrl.prototype = {
+ constructor: BreadcrumbsCtrl,
+
+ /**
+ * Register the breadcrumbs controller.
+ */
+ register: function () {
+ if (serviceProvider.breadcrumbsCtrl === undefined) {
+ serviceProvider.register('breadcrumbsCtrl', breadcrumbsCtrl);
+ }
+ },
+
+ /**
+ * Generate the breadcrumbs.
+ *
+ * @param {object} breadcrumbEntity The breadcrumb
+ */
+ generateBreadcrumbs: function (breadcrumbEntity) {
+ var label = breadcrumbEntity.id;
+ if (breadcrumbEntity.permissions.canRead) {
+ label = breadcrumbEntity.breadcrumb.name;
+ }
+
+ this.breadcrumbs.unshift($.extend({
+ 'label': label
+ }, breadcrumbEntity));
+
+ if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.parentBreadcrumb)) {
+ this.generateBreadcrumbs(breadcrumbEntity.parentBreadcrumb);
+ }
+ },
+
+ /**
+ * Updates the version control information for the specified process group.
+ *
+ * @param processGroupId
+ * @param versionControlInformation
+ */
+ updateVersionControlInformation: function (processGroupId, versionControlInformation) {
+ $.each(this.breadcrumbs, function (_, breadcrumbEntity) {
+ if (breadcrumbEntity.id === processGroupId) {
+ breadcrumbEntity.breadcrumb.versionControlInformation = versionControlInformation;
+ return false;
+ }
+ });
+ },
+
+ /**
+ * Reset the breadcrumbs.
+ */
+ resetBreadcrumbs: function () {
+ this.breadcrumbs = [];
+ },
+
+ /**
+ * Whether this crumb is tracking.
+ *
+ * @param breadcrumbEntity
+ * @returns {*}
+ */
+ isTracking: function (breadcrumbEntity) {
+ return nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState);
+ },
+
+ /**
+ * Returns the class string to use for the version control of the specified breadcrumb.
+ *
+ * @param breadcrumbEntity
+ * @returns {string}
+ */
+ getVersionControlClass: function (breadcrumbEntity) {
+ if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState)) {
+ var vciState = breadcrumbEntity.versionedFlowState;
+ if (vciState === 'SYNC_FAILURE') {
+ console.log("it is been sync failed..000");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return 'breadcrumb-version-control-gray fa fa-question'
+ } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
+ console.log("it is been locally modified and stale...000");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return 'breadcrumb-version-control-red fa fa-exclamation-circle';
+ } else if (vciState === 'STALE') {
+ console.log("it is been stale...000");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return 'breadcrumb-version-control-red fa fa-arrow-circle-up';
+ } else if (vciState === 'LOCALLY_MODIFIED') {
+ console.log("it is been locally modified...000");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return 'breadcrumb-version-control-gray fa fa-asterisk';
+ } else {
+ $('#environmentType').prop('disabled', false);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ return 'breadcrumb-version-control-green fa fa-check';
+ }
+ } else {
+ console.log("it is NOT been version controlled...000");
+ $('#environmentType').prop('disabled', true);
+ return '';
+ }
+ },
+
+ /**
+ * Gets the content for the version control tooltip for the specified breadcrumb.
+ *
+ * @param breadcrumbEntity
+ */
+ getVersionControlTooltip: function (breadcrumbEntity) {
+ if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState) && breadcrumbEntity.permissions.canRead) {
+ return nfCommon.getVersionControlTooltip(breadcrumbEntity.breadcrumb.versionControlInformation);
+ } else {
+ return 'This Process Group is not under version control.'
+ }
+ },
+
+ /**
+ * Get the breadcrumbs.
+ */
+ getBreadcrumbs: function () {
+ return this.breadcrumbs;
+ },
+
+ /**
+ * Update the breadcrumbs css.
+ *
+ * @param {object} style The style to be applied.
+ */
+ updateBreadcrumbsCss: function (style) {
+ $('#breadcrumbs').css(style);
+ },
+
+ /**
+ * Reset initial scroll position.
+ */
+ resetScrollPosition: function () {
+ var title = $('#data-flow-title-container');
+ var titlePosition = title.position();
+ var titleWidth = title.outerWidth();
+ var titleRight = titlePosition.left + titleWidth;
+
+ var padding = $('#breadcrumbs-right-border').width();
+ var viewport = $('#data-flow-title-viewport');
+ var viewportWidth = viewport.width();
+ var viewportRight = viewportWidth - padding;
+
+ // if the title's right is past the viewport's right, shift accordingly
+ if (titleRight > viewportRight) {
+ // adjust the position
+ title.css('left', (titlePosition.left - (titleRight - viewportRight)) + 'px');
+ } else {
+ title.css('left', '10px');
+ }
+ },
+
+ /**
+ * Registers a scroll event on the `element`
+ *
+ * @param {object} element The element event listener will be registered upon.
+ */
+ registerMouseWheelEvent: function (element) {
+ // mousewheel -> IE, Chrome
+ // DOMMouseScroll -> FF
+ // wheel -> FF, IE
+
+ // still having issues with this in IE :/
+ element.on('DOMMouseScroll mousewheel', function (evt, d) {
+ if (nfCommon.isUndefinedOrNull(evt.originalEvent)) {
+ return;
+ }
+
+ var title = $('#data-flow-title-container');
+ var titlePosition = title.position();
+ var titleWidth = title.outerWidth();
+ var titleRight = titlePosition.left + titleWidth;
+
+ var padding = $('#breadcrumbs-right-border').width();
+ var viewport = $('#data-flow-title-viewport');
+ var viewportWidth = viewport.width();
+ var viewportRight = viewportWidth - padding;
+
+ // if the width of the title is larger than the viewport
+ if (titleWidth > viewportWidth) {
+ var adjust = false;
+
+ var delta = 0;
+
+ //Chrome and Safari both have evt.originalEvent.detail defined but
+ //evt.originalEvent.wheelDelta holds the correct value so we must
+ //check for evt.originalEvent.wheelDelta first!
+ if (nfCommon.isDefinedAndNotNull(evt.originalEvent.wheelDelta)) {
+ delta = evt.originalEvent.wheelDelta;
+ } else if (nfCommon.isDefinedAndNotNull(evt.originalEvent.detail)) {
+ delta = -evt.originalEvent.detail;
+ }
+
+ // determine the increment
+ if (delta > 0 && titleRight > viewportRight) {
+ var increment = -25;
+ adjust = true;
+ } else if (delta < 0 && (titlePosition.left - padding) < 0) {
+ increment = 25;
+
+ // don't shift too far
+ if (titlePosition.left + increment > padding) {
+ increment = padding - titlePosition.left;
+ }
+
+ adjust = true;
+ }
+
+ if (adjust) {
+ // adjust the position
+ title.css('left', (titlePosition.left + increment) + 'px');
+ }
+ }
+ });
+ }
+ }
+
+ var breadcrumbsCtrl = new BreadcrumbsCtrl();
+ breadcrumbsCtrl.register();
+ return breadcrumbsCtrl;
+ }
+}));
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
new file mode 100644
index 0000000..0f4b953
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
@@ -0,0 +1,1150 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'Slick',
+ 'nf.Client',
+ 'nf.Birdseye',
+ 'nf.Storage',
+ 'nf.Graph',
+ 'nf.CanvasUtils',
+ 'nf.ErrorHandler',
+ 'nf.FilteredDialogCommon',
+ 'nf.Dialog',
+ 'nf.Common'],
+ function ($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon) {
+ return (nf.ng.ProcessorComponent = factory($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.ng.ProcessorComponent =
+ factory(require('jquery'),
+ require('Slick'),
+ require('nf.Client'),
+ require('nf.Birdseye'),
+ require('nf.Storage'),
+ require('nf.Graph'),
+ require('nf.CanvasUtils'),
+ require('nf.ErrorHandler'),
+ require('nf.FilteredDialogCommon'),
+ require('nf.Dialog'),
+ require('nf.Common')));
+ } else {
+ nf.ng.ProcessorComponent = factory(root.$,
+ root.Slick,
+ root.nf.Client,
+ root.nf.Birdseye,
+ root.nf.Storage,
+ root.nf.Graph,
+ root.nf.CanvasUtils,
+ root.nf.ErrorHandler,
+ root.nf.FilteredDialogCommon,
+ root.nf.Dialog,
+ root.nf.Common);
+ }
+}(this, function ($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon) {
+ 'use strict';
+
+ return function (serviceProvider) {
+ 'use strict';
+
+
+ var latestResponse;
+
+ var processorTypesData;
+
+
+ /**
+ * Filters the processor type table.
+ */
+ var applyFilter = function () {
+ // get the dataview
+ var processorTypesGrid = $('#processor-types-table').data('gridInstance');
+
+ // ensure the grid has been initialized
+ if (nfCommon.isDefinedAndNotNull(processorTypesGrid)) {
+ var processorTypesDataForFilter = processorTypesGrid.getData();
+
+ // update the search criteria
+ processorTypesDataForFilter.setFilterArgs({
+ searchString: getFilterText()
+ });
+ processorTypesDataForFilter.refresh();
+
+ // update the buttons to possibly trigger the disabled state
+ $('#new-processor-dialog').modal('refreshButtons');
+
+ // update the selection if possible
+ if (processorTypesDataForFilter.getLength() > 0) {
+ nfFilteredDialogCommon.choseFirstRow(processorTypesGrid);
+ // make the first row visible
+ processorTypesGrid.scrollRowToTop(0);
+ }
+ }
+ };
+
+ /**
+ * Determines if the item matches the filter.
+ *
+ * @param {object} item The item to filter.
+ * @param {object} args The filter criteria.
+ * @returns {boolean} Whether the item matches the filter.
+ */
+ var matchesRegex = function (item, args) {
+ if (args.searchString === '') {
+ return true;
+ }
+
+ try {
+ // perform the row filtering
+ var filterExp = new RegExp(args.searchString, 'i');
+ } catch (e) {
+ // invalid regex
+ return false;
+ }
+
+ // determine if the item matches the filter
+ var matchesLabel = item['label'].search(filterExp) >= 0;
+ var matchesTags = item['tags'].search(filterExp) >= 0;
+ return matchesLabel || matchesTags;
+ };
+
+ /**
+ * Performs the filtering.
+ *
+ * @param {object} item The item subject to filtering.
+ * @param {object} args Filter arguments.
+ * @returns {Boolean} Whether or not to include the item.
+ */
+ var filter = function (item, args) {
+ // determine if the item matches the filter
+ var matchesFilter = matchesRegex(item, args);
+
+ // determine if the row matches the selected tags
+ var matchesTags = true;
+ if (matchesFilter) {
+ var tagFilters = $('#processor-tag-cloud').tagcloud('getSelectedTags');
+ var hasSelectedTags = tagFilters.length > 0;
+ if (hasSelectedTags) {
+ matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+ }
+ }
+
+ // determine if the row matches the selected source group
+ var matchesGroup = true;
+ if (matchesFilter && matchesTags) {
+ var bundleGroup = $('#processor-bundle-group-combo').combo('getSelectedOption');
+ if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+ matchesGroup = (item.bundle.group === bundleGroup.value);
+ }
+ }
+
+ // determine if this row should be visible
+ var matches = matchesFilter && matchesTags && matchesGroup;
+
+ // if this row is currently selected and its being filtered
+ if (matches === false && $('#selected-processor-type').text() === item['type']) {
+ // clear the selected row
+ $('#processor-type-description').attr('title', '').text('');
+ $('#processor-type-name').attr('title', '').text('');
+ $('#processor-type-bundle').attr('title', '').text('');
+ $('#selected-processor-name').text('');
+ $('#selected-processor-type').text('').removeData('bundle');
+
+ // clear the active cell the it can be reselected when its included
+ var processTypesGrid = $('#processor-types-table').data('gridInstance');
+ processTypesGrid.resetActiveCell();
+ }
+
+ return matches;
+ };
+
+ /**
+ * Determines if the specified tags match all the tags selected by the user.
+ *
+ * @argument {string[]} tagFilters The tag filters.
+ * @argument {string} tags The tags to test.
+ */
+ var matchesSelectedTags = function (tagFilters, tags) {
+ var selectedTags = [];
+ $.each(tagFilters, function (_, filter) {
+ selectedTags.push(filter);
+ });
+
+ // normalize the tags
+ var normalizedTags = tags.toLowerCase();
+
+ var matches = true;
+ $.each(selectedTags, function (i, selectedTag) {
+ if (normalizedTags.indexOf(selectedTag) === -1) {
+ matches = false;
+ return false;
+ }
+ });
+
+ return matches;
+ };
+
+ /**
+ * Get the text out of the filter field. If the filter field doesn't
+ * have any text it will contain the text 'filter list' so this method
+ * accounts for that.
+ */
+ var getFilterText = function () {
+ return $('#processor-type-filter').val();
+ };
+
+ /**
+ * Resets the filtered processor types.
+ */
+ var resetProcessorDialog = function () {
+
+ //********* REPLICATED A BLOCK OF CODE to get logic of autoloading processor ---STARTING FROM HERE----********************
+
+ // initialize the processor type table
+ var processorTypesColumns = [
+ {
+ id: 'type',
+ name: 'Type',
+ field: 'label',
+ formatter: nfCommon.typeFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'version',
+ name: 'Version',
+ field: 'version',
+ formatter: nfCommon.typeVersionFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'tags',
+ name: 'Tags',
+ field: 'tags',
+ sortable: true,
+ resizable: true,
+ formatter: nfCommon.genericValueFormatter
+ }
+ ];
+
+ var processorTypesOptions = {
+ forceFitColumns: true,
+ enableTextSelectionOnCells: true,
+ enableCellNavigation: true,
+ enableColumnReorder: false,
+ autoEdit: false,
+ multiSelect: false,
+ rowHeight: 24
+ };
+
+ // initialize the dataview
+ processorTypesData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ processorTypesData.setItems([]);
+ processorTypesData.setFilterArgs({
+ searchString: getFilterText()
+ });
+ processorTypesData.setFilter(filter);
+
+ // initialize the sort
+ nfCommon.sortType({
+ columnId: 'type',
+ sortAsc: true
+ }, processorTypesData);
+
+ // initialize the grid
+ var processorTypesGrid = new Slick.Grid('#processor-types-table', processorTypesData, processorTypesColumns, processorTypesOptions);
+ processorTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+ processorTypesGrid.registerPlugin(new Slick.AutoTooltips());
+ processorTypesGrid.setSortColumn('type', true);
+ processorTypesGrid.onSort.subscribe(function (e, args) {
+ nfCommon.sortType({
+ columnId: args.sortCol.field,
+ sortAsc: args.sortAsc
+ }, processorTypesData);
+ });
+ processorTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+ if ($.isArray(args.rows) && args.rows.length === 1) {
+ var processorTypeIndex = args.rows[0];
+ var processorType = processorTypesGrid.getDataItem(processorTypeIndex);
+
+ // set the processor type description
+ if (nfCommon.isDefinedAndNotNull(processorType)) {
+ if (nfCommon.isBlank(processorType.description)) {
+ $('#processor-type-description')
+ .attr('title', '')
+ .html('<span class="unset">No description specified</span>');
+ } else {
+ $('#processor-type-description')
+ .width($('#processor-description-container').innerWidth() - 1)
+ .html(processorType.description)
+ .ellipsis();
+ }
+
+ var bundle = nfCommon.formatBundle(processorType.bundle);
+ var type = nfCommon.formatType(processorType);
+
+ // populate the dom
+ $('#processor-type-name').text(type).attr('title', type);
+ $('#processor-type-bundle').text(bundle).attr('title', bundle);
+ $('#selected-processor-name').text(processorType.label);
+ $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle);
+
+ // refresh the buttons based on the current selection
+ $('#new-processor-dialog').modal('refreshButtons');
+ }
+ }
+ });
+ processorTypesGrid.onViewportChanged.subscribe(function (e, args) {
+ nfCommon.cleanUpTooltips($('#processor-types-table'), 'div.view-usage-restriction');
+ });
+
+ // wire up the dataview to the grid
+ processorTypesData.onRowCountChanged.subscribe(function (e, args) {
+ processorTypesGrid.updateRowCount();
+ processorTypesGrid.render();
+
+ // update the total number of displayed processors
+ $('#displayed-processor-types').text(args.current);
+ });
+ processorTypesData.onRowsChanged.subscribe(function (e, args) {
+ processorTypesGrid.invalidateRows(args.rows);
+ processorTypesGrid.render();
+ });
+ processorTypesData.syncGridSelection(processorTypesGrid, false);
+
+ // hold onto an instance of the grid
+ $('#processor-types-table').data('gridInstance', processorTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+ var usageRestriction = $(this).find('div.view-usage-restriction');
+ if (usageRestriction.length && !usageRestriction.data('qtip')) {
+ var rowId = $(this).find('span.row-id').text();
+
+ // get the status item
+ var item = processorTypesData.getItemById(rowId);
+
+ // show the tooltip
+ if (item.restricted === true) {
+ var restrictionTip = $('<div></div>');
+
+ if (nfCommon.isBlank(item.usageRestriction)) {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+ } else {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+ }
+
+ var restrictions = [];
+ if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+ $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+ restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+ });
+ } else {
+ restrictions.push('Access to restricted components regardless of restrictions.');
+ }
+ restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+ usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+ content: restrictionTip,
+ position: {
+ container: $('#summary'),
+ at: 'bottom right',
+ my: 'top left',
+ adjust: {
+ x: 4,
+ y: 4
+ }
+ }
+ }));
+ }
+ }
+ });
+
+ var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+ // load the available processor types, this select is shown in the
+ // new processor dialog when a processor is dragged onto the screen
+ $.ajax({
+ type: 'GET',
+ url:'../nifi-api/flow/processor-types',
+ // url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.processorTypes,
+ dataType: 'json'
+ }).done(function (response) {
+ console.log(response);
+ var tags = [];
+ var groups = d3.set();
+ var restrictedUsage = d3.map();
+ var requiredPermissions = d3.map();
+
+ // begin the update
+ processorTypesData.beginUpdate();
+
+ // go through each processor type
+ $.each(response.processorTypes, function (i, documentedType) {
+ var type = documentedType.type;
+
+ if (documentedType.restricted === true) {
+ if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+ $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+
+ // update required permissions
+ if (!requiredPermissions.has(requiredPermission.id)) {
+ requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(requiredPermission.id)) {
+ restrictedUsage.set(requiredPermission.id, []);
+ }
+
+ restrictedUsage.get(requiredPermission.id).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+ })
+ });
+ } else {
+ // update required permissions
+ if (!requiredPermissions.has(generalRestriction.value)) {
+ requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(generalRestriction.value)) {
+ restrictedUsage.set(generalRestriction.value, []);
+ }
+
+ restrictedUsage.get(generalRestriction.value).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+ });
+ }
+ }
+
+ // record the group
+ groups.add(documentedType.bundle.group);
+
+ // create the row for the processor type
+ processorTypesData.addItem({
+ id: i,
+ label: nfCommon.substringAfterLast(type, '.'),
+ type: type,
+ bundle: documentedType.bundle,
+ description: nfCommon.escapeHtml(documentedType.description),
+ restricted: documentedType.restricted,
+ usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+ explicitRestrictions: documentedType.explicitRestrictions,
+ tags: documentedType.tags.join(', ')
+ });
+
+ // count the frequency of each tag for this type
+ $.each(documentedType.tags, function (i, tag) {
+ tags.push(tag.toLowerCase());
+ });
+ });
+
+ // end the update
+ processorTypesData.endUpdate();
+
+ // resort
+ processorTypesData.reSort();
+ processorTypesGrid.invalidate();
+
+ // set the component restrictions and the corresponding required permissions
+ nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+ // set the total number of processors
+ $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length);
+
+ // create the tag cloud
+ $('#processor-tag-cloud').tagcloud({
+ tags: tags,
+ select: applyFilter,
+ remove: applyFilter
+ });
+
+ // build the combo options
+ var options = [{
+ text: 'all groups',
+ value: ''
+ }];
+ groups.each(function (group) {
+ options.push({
+ text: group,
+ value: group
+ });
+ });
+
+ // initialize the bundle group combo
+ $('#processor-bundle-group-combo').combo({
+ options: options,
+ select: applyFilter
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+
+//************* REPLICATED CODE---ENDS HERE------******************
+
+
+
+ // clear the selected tag cloud
+ $('#processor-tag-cloud').tagcloud('clearSelectedTags');
+
+ // reset the group combo
+ $('#processor-bundle-group-combo').combo('setSelectedOption', {
+ value: ''
+ });
+
+ // clear any filter strings
+ $('#processor-type-filter').val('');
+
+ // reapply the filter
+ applyFilter();
+
+ // clear the selected row
+ $('#processor-type-description').attr('title', '').text('');
+ $('#processor-type-name').attr('title', '').text('');
+ $('#processor-type-bundle').attr('title', '').text('');
+ $('#selected-processor-name').text('');
+ $('#selected-processor-type').text('').removeData('bundle');
+
+ // unselect any current selection
+ var processTypesGrid = $('#processor-types-table').data('gridInstance');
+ processTypesGrid.setSelectedRows([]);
+ processTypesGrid.resetActiveCell();
+ };
+
+ /**
+ * Create the processor and add to the graph.
+ *
+ * @argument {string} name The processor name.
+ * @argument {string} processorType The processor type.
+ * @argument {object} bundle The processor bundle.
+ * @argument {object} pt The point that the processor was dropped.
+ */
+ var createProcessor = function (name, processorType, bundle, pt) {
+ var processorEntity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': 0
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'type': processorType,
+ 'bundle': bundle,
+ 'name': name,
+ 'position': {
+ 'x': pt.x,
+ 'y': pt.y
+ }
+ }
+ };
+
+ // create a new processor of the defined type
+ $.ajax({
+ type: 'POST',
+ url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/processors',
+ data: JSON.stringify(processorEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ // add the processor to the graph
+ nfGraph.add({
+ 'processors': [response]
+ }, {
+ 'selectAll': true
+ });
+
+ // update component visibility
+ nfGraph.updateVisibility();
+
+ // update the birdseye
+ nfBirdseye.refresh();
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Whether the specified item is selectable.
+ *
+ * @param item process type
+ */
+ var isSelectable = function (item) {
+ return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions);
+ };
+
+ function ProcessorComponent() {
+
+ this.icon = 'icon icon-processor';
+
+ this.hoverIcon = 'icon icon-processor-add';
+
+ /**
+ * The processor component's modal.
+ */
+ this.modal = {
+
+ /**
+ * The processor component modal's filter.
+ */
+ filter: {
+
+ /**
+ * Initialize the filter.
+ */
+ init: function () {
+ // initialize the processor type table
+ var processorTypesColumns = [
+ {
+ id: 'type',
+ name: 'Type',
+ field: 'label',
+ formatter: nfCommon.typeFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'version',
+ name: 'Version',
+ field: 'version',
+ formatter: nfCommon.typeVersionFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'tags',
+ name: 'Tags',
+ field: 'tags',
+ sortable: true,
+ resizable: true,
+ formatter: nfCommon.genericValueFormatter
+ }
+ ];
+
+ var processorTypesOptions = {
+ forceFitColumns: true,
+ enableTextSelectionOnCells: true,
+ enableCellNavigation: true,
+ enableColumnReorder: false,
+ autoEdit: false,
+ multiSelect: false,
+ rowHeight: 24
+ };
+
+ // initialize the dataview
+ processorTypesData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ processorTypesData.setItems([]);
+ processorTypesData.setFilterArgs({
+ searchString: getFilterText()
+ });
+ processorTypesData.setFilter(filter);
+
+ // initialize the sort
+ nfCommon.sortType({
+ columnId: 'type',
+ sortAsc: true
+ }, processorTypesData);
+
+ // initialize the grid
+ var processorTypesGrid = new Slick.Grid('#processor-types-table', processorTypesData, processorTypesColumns, processorTypesOptions);
+ processorTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+ processorTypesGrid.registerPlugin(new Slick.AutoTooltips());
+ processorTypesGrid.setSortColumn('type', true);
+ processorTypesGrid.onSort.subscribe(function (e, args) {
+ nfCommon.sortType({
+ columnId: args.sortCol.field,
+ sortAsc: args.sortAsc
+ }, processorTypesData);
+ });
+ processorTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+ if ($.isArray(args.rows) && args.rows.length === 1) {
+ var processorTypeIndex = args.rows[0];
+ var processorType = processorTypesGrid.getDataItem(processorTypeIndex);
+
+ // set the processor type description
+ if (nfCommon.isDefinedAndNotNull(processorType)) {
+ if (nfCommon.isBlank(processorType.description)) {
+ $('#processor-type-description')
+ .attr('title', '')
+ .html('<span class="unset">No description specified</span>');
+ } else {
+ $('#processor-type-description')
+ .width($('#processor-description-container').innerWidth() - 1)
+ .html(processorType.description)
+ .ellipsis();
+ }
+
+ var bundle = nfCommon.formatBundle(processorType.bundle);
+ var type = nfCommon.formatType(processorType);
+
+ // populate the dom
+ $('#processor-type-name').text(type).attr('title', type);
+ $('#processor-type-bundle').text(bundle).attr('title', bundle);
+ $('#selected-processor-name').text(processorType.label);
+ $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle);
+
+ // refresh the buttons based on the current selection
+ $('#new-processor-dialog').modal('refreshButtons');
+ }
+ }
+ });
+ processorTypesGrid.onViewportChanged.subscribe(function (e, args) {
+ nfCommon.cleanUpTooltips($('#processor-types-table'), 'div.view-usage-restriction');
+ });
+
+ // wire up the dataview to the grid
+ processorTypesData.onRowCountChanged.subscribe(function (e, args) {
+ processorTypesGrid.updateRowCount();
+ processorTypesGrid.render();
+
+ // update the total number of displayed processors
+ $('#displayed-processor-types').text(args.current);
+ });
+ processorTypesData.onRowsChanged.subscribe(function (e, args) {
+ processorTypesGrid.invalidateRows(args.rows);
+ processorTypesGrid.render();
+ });
+ processorTypesData.syncGridSelection(processorTypesGrid, false);
+
+ // hold onto an instance of the grid
+ $('#processor-types-table').data('gridInstance', processorTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+ var usageRestriction = $(this).find('div.view-usage-restriction');
+ if (usageRestriction.length && !usageRestriction.data('qtip')) {
+ var rowId = $(this).find('span.row-id').text();
+
+ // get the status item
+ var item = processorTypesData.getItemById(rowId);
+
+ // show the tooltip
+ if (item.restricted === true) {
+ var restrictionTip = $('<div></div>');
+
+ if (nfCommon.isBlank(item.usageRestriction)) {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+ } else {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+ }
+
+ var restrictions = [];
+ if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+ $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+ restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+ });
+ } else {
+ restrictions.push('Access to restricted components regardless of restrictions.');
+ }
+ restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+ usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+ content: restrictionTip,
+ position: {
+ container: $('#summary'),
+ at: 'bottom right',
+ my: 'top left',
+ adjust: {
+ x: 4,
+ y: 4
+ }
+ }
+ }));
+ }
+ }
+ });
+
+ var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+ // load the available processor types, this select is shown in the
+ // new processor dialog when a processor is dragged onto the screen
+ $.ajax({
+ type: 'GET',
+ url:'../nifi-api/flow/processor-types',
+// url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.processorTypes,
+ dataType: 'json'
+ }).done(function (response) {
+ console.log(response);
+ var tags = [];
+ var groups = d3.set();
+ var restrictedUsage = d3.map();
+ var requiredPermissions = d3.map();
+
+ // begin the update
+ processorTypesData.beginUpdate();
+
+ // go through each processor type
+ $.each(response.processorTypes, function (i, documentedType) {
+ var type = documentedType.type;
+
+ if (documentedType.restricted === true) {
+ if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+ $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+
+ // update required permissions
+ if (!requiredPermissions.has(requiredPermission.id)) {
+ requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(requiredPermission.id)) {
+ restrictedUsage.set(requiredPermission.id, []);
+ }
+
+ restrictedUsage.get(requiredPermission.id).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+ })
+ });
+ } else {
+ // update required permissions
+ if (!requiredPermissions.has(generalRestriction.value)) {
+ requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(generalRestriction.value)) {
+ restrictedUsage.set(generalRestriction.value, []);
+ }
+
+ restrictedUsage.get(generalRestriction.value).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+ });
+ }
+ }
+
+ // record the group
+ groups.add(documentedType.bundle.group);
+
+ // create the row for the processor type
+ processorTypesData.addItem({
+ id: i,
+ label: nfCommon.substringAfterLast(type, '.'),
+ type: type,
+ bundle: documentedType.bundle,
+ description: nfCommon.escapeHtml(documentedType.description),
+ restricted: documentedType.restricted,
+ usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+ explicitRestrictions: documentedType.explicitRestrictions,
+ tags: documentedType.tags.join(', ')
+ });
+
+ // count the frequency of each tag for this type
+ $.each(documentedType.tags, function (i, tag) {
+ tags.push(tag.toLowerCase());
+ });
+ });
+
+ // end the update
+ processorTypesData.endUpdate();
+
+ // resort
+ processorTypesData.reSort();
+ processorTypesGrid.invalidate();
+
+ // set the component restrictions and the corresponding required permissions
+ nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+ // set the total number of processors
+ $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length);
+
+ // create the tag cloud
+ $('#processor-tag-cloud').tagcloud({
+ tags: tags,
+ select: applyFilter,
+ remove: applyFilter
+ });
+
+ // build the combo options
+ var options = [{
+ text: 'all groups',
+ value: ''
+ }];
+ groups.each(function (group) {
+ options.push({
+ text: group,
+ value: group
+ });
+ });
+
+ // initialize the bundle group combo
+ $('#processor-bundle-group-combo').combo({
+ options: options,
+ select: applyFilter
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ }
+ },
+
+ /**
+ * Gets the modal element.
+ *
+ * @returns {*|jQuery|HTMLElement}
+ */
+ getElement: function () {
+ return $('#new-processor-dialog');
+ },
+
+ /**
+ * Initialize the modal.
+ */
+ init: function () {
+ this.filter.init();
+
+ // configure the new processor dialog
+ this.getElement().modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Add Processor',
+ handler: {
+ resize: function () {
+ $('#processor-type-description')
+ .width($('#processor-description-container').innerWidth() - 1)
+ .text($('#processor-type-description').attr('title'))
+ .ellipsis();
+ }
+ }
+ });
+ },
+
+ /**
+ * Updates the modal config.
+ *
+ * @param {string} name The name of the property to update.
+ * @param {object|array} config The config for the `name`.
+ */
+ update: function (name, config) {
+ this.getElement().modal(name, config);
+ },
+
+ /**
+ * Show the modal
+ */
+ show: function () {
+ this.getElement().modal('show');
+ },
+
+ /**
+ * Hide the modal
+ */
+ hide: function () {
+ this.getElement().modal('hide');
+ }
+ };
+ }
+
+ ProcessorComponent.prototype = {
+ constructor: ProcessorComponent,
+
+ /**
+ * Gets the component.
+ *
+ * @returns {*|jQuery|HTMLElement}
+ */
+ getElement: function () {
+ return $('#processor-component');
+ },
+
+ /**
+ * Enable the component.
+ */
+ enabled: function () {
+ this.getElement().attr('disabled', false);
+ },
+
+ /**
+ * Disable the component.
+ */
+ disabled: function () {
+ this.getElement().attr('disabled', true);
+ },
+
+ /**
+ * Handler function for when component is dropped on the canvas.
+ *
+ * @argument {object} pt The point that the component was dropped
+ */
+ dropHandler: function (pt) {
+ this.promptForProcessorType(pt);
+ },
+
+ /**
+ * The drag icon for the toolbox component.
+ *
+ * @param event
+ * @returns {*|jQuery|HTMLElement}
+ */
+ dragIcon: function (event) {
+ return $('<div class="icon icon-processor-add"></div>');
+ },
+
+ /**
+ * Prompts the user to select the type of new processor to create.
+ *
+ * @argument {object} pt The point that the processor was dropped
+ */
+ promptForProcessorType: function (pt) {
+ var processorComponent = this;
+
+ // handles adding the selected processor at the specified point
+ var addProcessor = function () {
+ // get the type of processor currently selected
+ var name = $('#selected-processor-name').text();
+ var processorType = $('#selected-processor-type').text();
+ var bundle = $('#selected-processor-type').data('bundle');
+
+ // ensure something was selected
+ if (name === '' || processorType === '') {
+ nfDialog.showOkDialog({
+ headerText: 'Add Processor',
+ dialogContent: 'The type of processor to create must be selected.'
+ });
+ } else {
+ // create the new processor
+ createProcessor(name, processorType, bundle, pt);
+ }
+
+ // hide the dialog
+ processorComponent.modal.hide();
+ };
+
+ // get the grid reference
+ var grid = $('#processor-types-table').data('gridInstance');
+ var dataview = grid.getData();
+
+ // add the processor when its double clicked in the table
+ var gridDoubleClick = function (e, args) {
+ var processorType = grid.getDataItem(args.row);
+
+ if (isSelectable(processorType)) {
+ $('#selected-processor-name').text(processorType.label);
+ $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle);
+
+ addProcessor();
+ }
+ };
+
+ // register a handler for double click events
+ grid.onDblClick.subscribe(gridDoubleClick);
+
+ // update the button model
+ this.modal.update('setButtonModel', [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: function () {
+ var selected = grid.getSelectedRows();
+
+ if (selected.length > 0) {
+ // grid configured with multi-select = false
+ var item = grid.getDataItem(selected[0]);
+ return isSelectable(item) === false;
+ } else {
+ return dataview.getLength() === 0;
+ }
+ },
+ handler: {
+ click: addProcessor
+ }
+ },
+ {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $('#new-processor-dialog').modal('hide');
+ }
+ }
+ }]);
+
+ // set a new handler for closing the the dialog
+ this.modal.update('setCloseHandler', function () {
+ // remove the handler
+ grid.onDblClick.unsubscribe(gridDoubleClick);
+
+ // clear the current filters
+ resetProcessorDialog();
+ });
+
+ // show the dialog
+ this.modal.show();
+
+ var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+ // setup the filter
+ $('#processor-type-filter').off('keyup').on('keyup', function (e) {
+ var code = e.keyCode ? e.keyCode : e.which;
+
+ // ignore navigation keys
+ if ($.inArray(code, navigationKeys) !== -1) {
+ return;
+ }
+
+ if (code === $.ui.keyCode.ENTER) {
+ var selected = grid.getSelectedRows();
+
+ if (selected.length > 0) {
+ // grid configured with multi-select = false
+ var item = grid.getDataItem(selected[0]);
+ if (isSelectable(item)) {
+ addProcessor();
+ }
+ }
+ } else {
+ applyFilter();
+ }
+ });
+
+ // setup row navigation
+ nfFilteredDialogCommon.addKeydownListener('#processor-type-filter', grid, dataview);
+
+ // adjust the grid canvas now that its been rendered
+ grid.resizeCanvas();
+
+ // auto select the first row if possible
+ if (dataview.getLength() > 0) {
+ nfFilteredDialogCommon.choseFirstRow(grid);
+ }
+
+ // set the initial focus
+ $('#processor-type-filter').focus()
+ }
+ };
+
+ var processorComponent = new ProcessorComponent();
+ return processorComponent;
+ };
+}));
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js
new file mode 100644
index 0000000..0cdb1a1
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js
@@ -0,0 +1,1587 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'd3',
+ 'nf.ErrorHandler',
+ 'nf.Common',
+ 'nf.Dialog',
+ 'nf.Storage',
+ 'nf.Client',
+ 'nf.CanvasUtils',
+ 'nf.Connection'],
+ function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
+ return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.ConnectionConfiguration =
+ factory(require('jquery'),
+ require('d3'),
+ require('nf.ErrorHandler'),
+ require('nf.Common'),
+ require('nf.Dialog'),
+ require('nf.Storage'),
+ require('nf.Client'),
+ require('nf.CanvasUtils'),
+ require('nf.Connection')));
+ } else {
+ nf.ConnectionConfiguration = factory(root.$,
+ root.d3,
+ root.nf.ErrorHandler,
+ root.nf.Common,
+ root.nf.Dialog,
+ root.nf.Storage,
+ root.nf.Client,
+ root.nf.CanvasUtils,
+ root.nf.Connection);
+ }
+}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
+ 'use strict';
+
+ var nfBirdseye;
+ var nfGraph;
+
+ var defaultBackPressureObjectThreshold;
+ var defaultBackPressureDataSizeThreshold;
+
+ var CONNECTION_OFFSET_Y_INCREMENT = 75;
+ var CONNECTION_OFFSET_X_INCREMENT = 200;
+
+ var config = {
+ urls: {
+ api: '../nifi-api',
+ prioritizers: '../nifi-api/flow/prioritizers'
+ }
+ };
+
+ /**
+ * Removes the temporary if necessary.
+ */
+ var removeTempEdge = function () {
+ d3.select('path.connector').remove();
+ };
+
+ /**
+ * Activates dialog's button model refresh on a connection relationships change.
+ */
+ var addDialogRelationshipsChangeListener = function() {
+ // refresh button model when a relationship selection changes
+ $('div.available-relationship').bind('change', function() {
+ $('#connection-configuration').modal('refreshButtons');
+ });
+ }
+
+ /**
+ * Initializes the source in the new connection dialog.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceNewConnectionDialog = function (source) {
+ // handle the selected source
+ if (nfCanvasUtils.isProcessor(source)) {
+ return $.Deferred(function (deferred) {
+ // initialize the source processor
+ initializeSourceProcessor(source).done(function (processor) {
+ if (!nfCommon.isEmpty(processor.relationships)) {
+ // populate the available connections
+ $.each(processor.relationships, function (i, relationship) {
+ createRelationshipOption(relationship.name);
+ });
+
+ // resolve the deferred
+ deferred.resolve();
+ } else {
+ // there are no relationships for this processor
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: '\'' + nfCommon.escapeHtml(processor.name) + '\' does not support any relationships.'
+ });
+
+ // reset the dialog
+ resetDialog();
+
+ deferred.reject();
+ }
+ }).fail(function () {
+ deferred.reject();
+ });
+ }).promise();
+ } else {
+ return $.Deferred(function (deferred) {
+ // determine how to initialize the source
+ var connectionSourceDeferred;
+ if (nfCanvasUtils.isInputPort(source)) {
+ connectionSourceDeferred = initializeSourceInputPort(source);
+ } else if (nfCanvasUtils.isRemoteProcessGroup(source)) {
+ connectionSourceDeferred = initializeSourceRemoteProcessGroup(source);
+ } else if (nfCanvasUtils.isProcessGroup(source)) {
+ connectionSourceDeferred = initializeSourceProcessGroup(source);
+ } else {
+ connectionSourceDeferred = initializeSourceFunnel(source);
+ }
+
+ // finish initialization when appropriate
+ connectionSourceDeferred.done(function () {
+ deferred.resolve();
+ }).fail(function () {
+ deferred.reject();
+ });
+ }).promise();
+ }
+ };
+
+ /**
+ * Initializes the source when the source is an input port.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceInputPort = function (source) {
+ return $.Deferred(function (deferred) {
+ // get the input port data
+ var inputPortData = source.datum();
+ var inputPortName = inputPortData.permissions.canRead ? inputPortData.component.name : inputPortData.id;
+
+ // populate the port information
+ $('#input-port-source').show();
+ $('#input-port-source-name').text(inputPortName).attr('title', inputPortName);
+
+ // populate the connection source details
+ $('#connection-source-id').val(inputPortData.id);
+ $('#connection-source-component-id').val(inputPortData.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
+
+ // resolve the deferred
+ deferred.resolve();
+ }).promise();
+ };
+
+ /**
+ * Initializes the source when the source is an input port.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceFunnel = function (source) {
+ return $.Deferred(function (deferred) {
+ // get the funnel data
+ var funnelData = source.datum();
+
+ // populate the port information
+ $('#funnel-source').show();
+
+ // populate the connection source details
+ $('#connection-source-id').val(funnelData.id);
+ $('#connection-source-component-id').val(funnelData.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
+
+ // resolve the deferred
+ deferred.resolve();
+ }).promise();
+ };
+
+ /**
+ * Initializes the source when the source is a processor.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceProcessor = function (source) {
+ return $.Deferred(function (deferred) {
+ // get the processor data
+ var processorData = source.datum();
+ var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
+ var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';
+
+ // populate the source processor information
+ $('#processor-source').show();
+ $('#processor-source-name').text(processorName).attr('title', processorName);
+ $('#processor-source-type').text(processorType).attr('title', processorType);
+
+ // populate the connection source details
+ $('#connection-source-id').val(processorData.id);
+ $('#connection-source-component-id').val(processorData.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
+
+ // show the available relationships
+ $('#relationship-names-container').show();
+
+ deferred.resolve(processorData.component);
+ });
+ };
+
+ /**
+ * Initializes the source when the source is a process group.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceProcessGroup = function (source) {
+ return $.Deferred(function (deferred) {
+ // get the process group data
+ var processGroupData = source.datum();
+
+ $.ajax({
+ type: 'GET',
+ url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
+ dataType: 'json'
+ }).done(function (response) {
+ var processGroup = response.processGroupFlow;
+ var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
+ var processGroupContents = processGroup.flow;
+
+ // show the output port options
+ var options = [];
+ $.each(processGroupContents.outputPorts, function (i, outputPort) {
+ // require explicit access to the output port as it's the source of the connection
+ if (outputPort.permissions.canRead && outputPort.permissions.canWrite) {
+ var component = outputPort.component;
+ options.push({
+ text: component.name,
+ value: component.id,
+ description: nfCommon.escapeHtml(component.comments)
+ });
+ }
+ });
+
+ // only proceed if there are output ports
+ if (!nfCommon.isEmpty(options)) {
+ $('#output-port-source').show();
+
+ // sort the options
+ options.sort(function (a, b) {
+ return a.text.localeCompare(b.text);
+ });
+
+ // create the combo
+ $('#output-port-options').combo({
+ options: options,
+ maxHeight: 300,
+ select: function (option) {
+ $('#connection-source-id').val(option.value);
+ }
+ });
+
+ // populate the connection details
+ $('#connection-source-component-id').val(processGroup.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(processGroup.id);
+ $('#connection-source-group-name').text(processGroupName);
+
+ deferred.resolve();
+ } else {
+ var message = '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any output ports.';
+ if (nfCommon.isEmpty(processGroupContents.outputPorts) === false) {
+ message = 'Not authorized for any output ports in \'' + nfCommon.escapeHtml(processGroupName) + '\'.';
+ }
+
+ // there are no output ports for this process group
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: message
+ });
+
+ // reset the dialog
+ resetDialog();
+
+ deferred.reject();
+ }
+ }).fail(function (xhr, status, error) {
+ // handle the error
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+
+ deferred.reject();
+ });
+ }).promise();
+ };
+
+ /**
+ * Initializes the source when the source is a remote process group.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceRemoteProcessGroup = function (source) {
+ return $.Deferred(function (deferred) {
+ // get the remote process group data
+ var remoteProcessGroupData = source.datum();
+
+ $.ajax({
+ type: 'GET',
+ url: remoteProcessGroupData.uri,
+ dataType: 'json'
+ }).done(function (response) {
+ var remoteProcessGroup = response.component;
+ var remoteProcessGroupContents = remoteProcessGroup.contents;
+
+ // only proceed if there are output ports
+ if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) {
+ $('#output-port-source').show();
+
+ // show the output port options
+ var options = [];
+ $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) {
+ options.push({
+ text: outputPort.name,
+ value: outputPort.id,
+ disabled: outputPort.exists === false,
+ description: nfCommon.escapeHtml(outputPort.comments)
+ });
+ });
+
+ // sort the options
+ options.sort(function (a, b) {
+ return a.text.localeCompare(b.text);
+ });
+
+ // create the combo
+ $('#output-port-options').combo({
+ options: options,
+ maxHeight: 300,
+ select: function (option) {
+ $('#connection-source-id').val(option.value);
+ }
+ });
+
+ // populate the connection details
+ $('#connection-source-component-id').val(remoteProcessGroup.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(remoteProcessGroup.id);
+ $('#connection-source-group-name').text(remoteProcessGroup.name);
+
+ deferred.resolve();
+ } else {
+ // there are no relationships for this processor
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any output ports.'
+ });
+
+ // reset the dialog
+ resetDialog();
+
+ deferred.reject();
+ }
+ }).fail(function (xhr, status, error) {
+ // handle the error
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+
+ deferred.reject();
+ });
+ }).promise();
+ };
+
+ var initializeDestinationNewConnectionDialog = function (destination) {
+ if (nfCanvasUtils.isOutputPort(destination)) {
+ return initializeDestinationOutputPort(destination);
+ } else if (nfCanvasUtils.isProcessor(destination)) {
+ return $.Deferred(function (deferred) {
+ initializeDestinationProcessor(destination).done(function (processor) {
+ // Need to add the destination relationships because we need to
+ // provide this to wire up the publishers and subscribers correctly
+ // for a given connection since processors can have multiple
+ // relationships
+ $.each(processor.relationships, function (i, relationship) {
+ createRelationshipOption(relationship.name);
+ });
+
+ deferred.resolve();
+ }).fail(function () {
+ deferred.reject();
+ });
+ }).promise();
+ } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
+ return initializeDestinationRemoteProcessGroup(destination);
+ } else if (nfCanvasUtils.isFunnel(destination)) {
+ return initializeDestinationFunnel(destination);
+ } else {
+ return initializeDestinationProcessGroup(destination);
+ }
+ };
+
+ var initializeDestinationOutputPort = function (destination) {
+ return $.Deferred(function (deferred) {
+ var outputPortData = destination.datum();
+ var outputPortName = outputPortData.permissions.canRead ? outputPortData.component.name : outputPortData.id;
+
+ $('#output-port-destination').show();
+ $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName);
+
+ // populate the connection destination details
+ $('#connection-destination-id').val(outputPortData.id);
+ $('#connection-destination-component-id').val(outputPortData.id);
+
+ // populate the group details
+ $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
+
+ deferred.resolve();
+ }).promise();
+ };
+
+ var initializeDestinationFunnel = function (destination) {
+ return $.Deferred(function (deferred) {
+ var funnelData = destination.datum();
+
+ $('#funnel-destination').show();
+
+ // populate the connection destination details
+ $('#connection-destination-id').val(funnelData.id);
+ $('#connection-destination-component-id').val(funnelData.id);
+
+ // populate the group details
+ $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
+
+ deferred.resolve();
+ }).promise();
+ };
+
+ var initializeDestinationProcessor = function (destination) {
+ return $.Deferred(function (deferred) {
+ var processorData = destination.datum();
+ var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
+ var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';
+
+ $('#processor-destination').show();
+ $('#processor-destination-name').text(processorName).attr('title', processorName);
+ $('#processor-destination-type').text(processorType).attr('title', processorType);
+
+ // populate the connection destination details
+ $('#connection-destination-id').val(processorData.id);
+ $('#connection-destination-component-id').val(processorData.id);
+
+ // populate the group details
+ $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
+ $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
+
+ deferred.resolve(processorData.component);
+ }).promise();
+ };
+
+ /**
+ * Initializes the destination when the destination is a process group.
+ *
+ * @argument {selection} destination The destination
+ */
+ var initializeDestinationProcessGroup = function (destination) {
+ return $.Deferred(function (deferred) {
+ var processGroupData = destination.datum();
+
+ $.ajax({
+ type: 'GET',
+ url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
+ dataType: 'json'
+ }).done(function (response) {
+ var processGroup = response.processGroupFlow;
+ var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
+ var processGroupContents = processGroup.flow;
+
+ // show the input port options
+ var options = [];
+ $.each(processGroupContents.inputPorts, function (i, inputPort) {
+ options.push({
+ text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id,
+ value: inputPort.id,
+ description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null
+ });
+ });
+
+ // only proceed if there are output ports
+ if (!nfCommon.isEmpty(options)) {
+ $('#input-port-destination').show();
+
+ // sort the options
+ options.sort(function (a, b) {
+ return a.text.localeCompare(b.text);
+ });
+
+ // create the combo
+ $('#input-port-options').combo({
+ options: options,
+ maxHeight: 300,
+ select: function (option) {
+ $('#connection-destination-id').val(option.value);
+ }
+ });
+
+ // populate the connection details
+ $('#connection-destination-component-id').val(processGroup.id);
+
+ // populate the group details
+ $('#connection-destination-group-id').val(processGroup.id);
+ $('#connection-destination-group-name').text(processGroupName);
+
+ deferred.resolve();
+ } else {
+ // there are no relationships for this processor
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any input ports.'
+ });
+
+ // reset the dialog
+ resetDialog();
+
+ deferred.reject();
+ }
+ }).fail(function (xhr, status, error) {
+ // handle the error
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+
+ deferred.reject();
+ });
+ }).promise();
+ };
+
+ /**
+ * Initializes the source when the source is a remote process group.
+ *
+ * @argument {selection} destination The destination
+ * @argument {object} connectionDestination The connection destination object
+ */
+ var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) {
+ return $.Deferred(function (deferred) {
+ var remoteProcessGroupData = destination.datum();
+
+ $.ajax({
+ type: 'GET',
+ url: remoteProcessGroupData.uri,
+ dataType: 'json'
+ }).done(function (response) {
+ var remoteProcessGroup = response.component;
+ var remoteProcessGroupContents = remoteProcessGroup.contents;
+
+ // only proceed if there are output ports
+ if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) {
+ $('#input-port-destination').show();
+
+ // show the input port options
+ var options = [];
+ $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) {
+ options.push({
+ text: inputPort.name,
+ value: inputPort.id,
+ disabled: inputPort.exists === false,
+ description: nfCommon.escapeHtml(inputPort.comments)
+ });
+ });
+
+ // sort the options
+ options.sort(function (a, b) {
+ return a.text.localeCompare(b.text);
+ });
+
+ // create the combo
+ $('#input-port-options').combo({
+ options: options,
+ maxHeight: 300,
+ select: function (option) {
+ $('#connection-destination-id').val(option.value);
+ }
+ });
+
+ // populate the connection details
+ $('#connection-destination-component-id').val(remoteProcessGroup.id);
+
+ // populate the group details
+ $('#connection-destination-group-id').val(remoteProcessGroup.id);
+ $('#connection-destination-group-name').text(remoteProcessGroup.name);
+
+ deferred.resolve();
+ } else {
+ // there are no relationships for this processor
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any input ports.'
+ });
+
+ // reset the dialog
+ resetDialog();
+
+ deferred.reject();
+ }
+ }).fail(function (xhr, status, error) {
+ // handle the error
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+
+ deferred.reject();
+ });
+ }).promise();
+ };
+
+ /**
+ * Initializes the source panel for groups.
+ *
+ * @argument {selection} source The source of the connection
+ */
+ var initializeSourceReadOnlyGroup = function (source) {
+ return $.Deferred(function (deferred) {
+ var sourceData = source.datum();
+ var sourceName = sourceData.permissions.canRead ? sourceData.component.name : sourceData.id;
+
+ // populate the port information
+ $('#read-only-output-port-source').show();
+
+ // populate the component information
+ $('#connection-source-component-id').val(sourceData.id);
+
+ // populate the group details
+ $('#connection-source-group-id').val(sourceData.id);
+ $('#connection-source-group-name').text(sourceName);
+
+ // resolve the deferred
+ deferred.resolve();
+ }).promise();
+ };
+
+ /**
+ * Initializes the source in the existing connection dialog.
+ *
+ * @argument {selection} source The source
+ */
+ var initializeSourceEditConnectionDialog = function (source) {
+ if (nfCanvasUtils.isProcessor(source)) {
+ return initializeSourceProcessor(source);
+ } else if (nfCanvasUtils.isInputPort(source)) {
+ return initializeSourceInputPort(source);
+ } else if (nfCanvasUtils.isFunnel(source)) {
+ return initializeSourceFunnel(source);
+ } else {
+ return initializeSourceReadOnlyGroup(source);
+ }
+ };
+
+ /**
+ * Initializes the destination in the existing connection dialog.
+ *
+ * @argument {selection} destination The destination
+ * @argument {object} connectionDestination The connection destination object
+ */
+ var initializeDestinationEditConnectionDialog = function (destination, connectionDestination) {
+ if (nfCanvasUtils.isProcessor(destination)) {
+ return initializeDestinationProcessor(destination);
+ } else if (nfCanvasUtils.isOutputPort(destination)) {
+ return initializeDestinationOutputPort(destination);
+ } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
+ return initializeDestinationRemoteProcessGroup(destination, connectionDestination);
+ } else if (nfCanvasUtils.isFunnel(destination)) {
+ return initializeDestinationFunnel(destination);
+ } else {
+ return initializeDestinationProcessGroup(destination);
+ }
+ };
+
+ /**
+ * Creates an option for the specified relationship name.
+ *
+ * @argument {string} name The relationship name
+ */
+ var createRelationshipOption = function (name) {
+ var nameSplit = name.split(":");
+ var nameLabel = name;
+
+ if (nameSplit.length > 1) {
+ // Example: publishes:data_transformation_format:1.0.0:message_router:stream_publish_url
+ var pubSub = nameSplit[0];
+ pubSub = pubSub.charAt(0).toUpperCase() + pubSub.slice(1);
+ nameLabel = pubSub + " " + nameSplit[1] + "/" + nameSplit[2] + " on " + nameSplit[4];
+ }
+
+ var relationshipLabel = $('<div class="relationship-name nf-checkbox-label ellipsis"></div>').text(nameLabel);
+ var relationshipValue = $('<span class="relationship-name-value hidden"></span>').text(name);
+ return $('<div class="available-relationship-container"><div class="available-relationship nf-checkbox checkbox-unchecked"></div>' +
+ '</div>').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names');
+ };
+
+ /**
+ * Adds a new connection.
+ *
+ * @argument {array} selectedRelationships The selected relationships
+ */
+ var addConnection = function (selectedRelationships) {
+ // get the connection details
+ var sourceId = $('#connection-source-id').val();
+ var destinationId = $('#connection-destination-id').val();
+
+ // get the selection components
+ var sourceComponentId = $('#connection-source-component-id').val();
+ var source = d3.select('#id-' + sourceComponentId);
+ var destinationComponentId = $('#connection-destination-component-id').val();
+ var destination = d3.select('#id-' + destinationComponentId);
+
+ // get the source/destination data
+ var sourceData = source.datum();
+ var destinationData = destination.datum();
+
+ // add bend points if we're dealing with a self loop
+ var bends = [];
+ if (sourceComponentId === destinationComponentId) {
+ var rightCenter = {
+ x: sourceData.position.x + (sourceData.dimensions.width),
+ y: sourceData.position.y + (sourceData.dimensions.height / 2)
+ };
+
+ var xOffset = nfConnection.config.selfLoopXOffset;
+ var yOffset = nfConnection.config.selfLoopYOffset;
+ bends.push({
+ 'x': (rightCenter.x + xOffset),
+ 'y': (rightCenter.y - yOffset)
+ });
+ bends.push({
+ 'x': (rightCenter.x + xOffset),
+ 'y': (rightCenter.y + yOffset)
+ });
+ } else {
+ var existingConnections = [];
+
+ // get all connections for the source component
+ var connectionsForSourceComponent = nfConnection.getComponentConnections(sourceComponentId);
+ $.each(connectionsForSourceComponent, function (_, connectionForSourceComponent) {
+ // get the id for the source/destination component
+ var connectionSourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionForSourceComponent);
+ var connectionDestinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent);
+
+ // if the connection is between these same components, consider it for collisions
+ if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) ||
+ (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) {
+
+ // record all connections between these two components in question
+ existingConnections.push(connectionForSourceComponent);
+ }
+ });
+
+ // if there are existing connections between these components, ensure the new connection won't collide
+ if (existingConnections.length > 0) {
+ var avoidCollision = false;
+ $.each(existingConnections, function (_, existingConnection) {
+ // only consider multiple connections with no bend points a collision, the existance of
+ // bend points suggests that the user has placed the connection into a desired location
+ if (nfCommon.isEmpty(existingConnection.bends)) {
+ avoidCollision = true;
+ return false;
+ }
+ });
+
+ // if we need to avoid a collision
+ if (avoidCollision === true) {
+ // determine the middle of the source/destination components
+ var sourceMiddle = [sourceData.position.x + (sourceData.dimensions.width / 2), sourceData.position.y + (sourceData.dimensions.height / 2)];
+ var destinationMiddle = [destinationData.position.x + (destinationData.dimensions.width / 2), destinationData.position.y + (destinationData.dimensions.height / 2)];
+
+ // detect if the line is more horizontal or vertical
+ var slope = ((sourceMiddle[1] - destinationMiddle[1]) / (sourceMiddle[0] - destinationMiddle[0]));
+ var isMoreHorizontal = slope <= 1 && slope >= -1;
+
+ // determines if the specified coordinate collides with another connection
+ var collides = function (x, y) {
+ var collides = false;
+ $.each(existingConnections, function (_, existingConnection) {
+ if (!nfCommon.isEmpty(existingConnection.bends)) {
+ if (isMoreHorizontal) {
+ // horizontal lines are adjusted in the y space
+ if (existingConnection.bends[0].y === y) {
+ collides = true;
+ return false;
+ }
+ } else {
+ // vertical lines are adjusted in the x space
+ if (existingConnection.bends[0].x === x) {
+ collides = true;
+ return false;
+ }
+ }
+ }
+ });
+ return collides;
+ };
+
+ // find the mid point on the connection
+ var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2;
+ var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2;
+
+ // attempt to position this connection so it doesn't collide
+ var xStep = isMoreHorizontal ? 0 : CONNECTION_OFFSET_X_INCREMENT;
+ var yStep = isMoreHorizontal ? CONNECTION_OFFSET_Y_INCREMENT : 0;
+ var positioned = false;
+ while (positioned === false) {
+ // consider above and below, then increment and try again (if necessary)
+ if (collides(xCandidate - xStep, yCandidate - yStep) === false) {
+ bends.push({
+ 'x': (xCandidate - xStep),
+ 'y': (yCandidate - yStep)
+ });
+ positioned = true;
+ } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) {
+ bends.push({
+ 'x': (xCandidate + xStep),
+ 'y': (yCandidate + yStep)
+ });
+ positioned = true;
+ }
+
+ if (isMoreHorizontal) {
+ yStep += CONNECTION_OFFSET_Y_INCREMENT;
+ } else {
+ xStep += CONNECTION_OFFSET_X_INCREMENT;
+ }
+ }
+ }
+ }
+ }
+
+ // determine the source group id
+ var sourceGroupId = $('#connection-source-group-id').val();
+ var destinationGroupId = $('#connection-destination-group-id').val();
+
+ // determine the source and destination types
+ var sourceType = nfCanvasUtils.getConnectableTypeForSource(source);
+ var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);
+
+ // get the settings
+ var connectionName = $('#connection-name').val();
+ var flowFileExpiration = $('#flow-file-expiration').val();
+ var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
+ var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
+ var prioritizers = $('#prioritizer-selected').sortable('toArray');
+ var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
+ var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
+ var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
+ var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';
+
+ if (validateSettings()) {
+ var connectionEntity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': 0
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'name': connectionName,
+ 'source': {
+ 'id': sourceId,
+ 'groupId': sourceGroupId,
+ 'type': sourceType
+ },
+ 'destination': {
+ 'id': destinationId,
+ 'groupId': destinationGroupId,
+ 'type': destinationType
+ },
+ 'selectedRelationships': selectedRelationships,
+ 'flowFileExpiration': flowFileExpiration,
+ 'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
+ 'backPressureObjectThreshold': backPressureObjectThreshold,
+ 'bends': bends,
+ 'prioritizers': prioritizers,
+ 'loadBalanceStrategy': loadBalanceStrategy,
+ 'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
+ 'loadBalanceCompression': loadBalanceCompression
+ }
+ };
+
+ // create the new connection
+ $.ajax({
+ type: 'POST',
+ url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections',
+ data: JSON.stringify(connectionEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ // add the connection
+ nfGraph.add({
+ 'connections': [response]
+ }, {
+ 'selectAll': true
+ });
+
+ // reload the connections source/destination components
+ nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
+
+ // update component visibility
+ nfGraph.updateVisibility();
+
+ // update the birdseye
+ nfBirdseye.refresh();
+ }).fail(function (xhr, status, error) {
+ // handle the error
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+ });
+ }
+ };
+
+ /**
+ * Updates an existing connection.
+ *
+ * @argument {array} selectedRelationships The selected relationships
+ */
+ var updateConnection = function (selectedRelationships) {
+ // get the connection details
+ var connectionId = $('#connection-id').text();
+ var connectionUri = $('#connection-uri').val();
+
+ // get the source details
+ var sourceComponentId = $('#connection-source-component-id').val();
+
+ // get the destination details
+ var destinationComponentId = $('#connection-destination-component-id').val();
+ var destination = d3.select('#id-' + destinationComponentId);
+ var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);
+
+ // get the destination details
+ var destinationId = $('#connection-destination-id').val();
+ var destinationGroupId = $('#connection-destination-group-id').val();
+
+ // get the settings
+ var connectionName = $('#connection-name').val();
+ var flowFileExpiration = $('#flow-file-expiration').val();
+ var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
+ var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
+ var prioritizers = $('#prioritizer-selected').sortable('toArray');
+ var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
+ var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
+ var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
+ var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';
+
+ if (validateSettings()) {
+ var d = nfConnection.get(connectionId);
+ var connectionEntity = {
+ 'revision': nfClient.getRevision(d),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'id': connectionId,
+ 'name': connectionName,
+ 'destination': {
+ 'id': destinationId,
+ 'groupId': destinationGroupId,
+ 'type': destinationType
+ },
+ 'selectedRelationships': selectedRelationships,
+ 'flowFileExpiration': flowFileExpiration,
+ 'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
+ 'backPressureObjectThreshold': backPressureObjectThreshold,
+ 'prioritizers': prioritizers,
+ 'loadBalanceStrategy': loadBalanceStrategy,
+ 'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
+ 'loadBalanceCompression': loadBalanceCompression
+ }
+ };
+
+ // update the connection
+ return $.ajax({
+ type: 'PUT',
+ url: connectionUri,
+ data: JSON.stringify(connectionEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ // update this connection
+ nfConnection.set(response);
+
+ // reload the connections source/destination components
+ nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
+ }).fail(function (xhr, status, error) {
+ if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: nfCommon.escapeHtml(xhr.responseText),
+ });
+ } else {
+ nfErrorHandler.handleAjaxError(xhr, status, error);
+ }
+ });
+ } else {
+ return $.Deferred(function (deferred) {
+ deferred.reject();
+ }).promise();
+ }
+ };
+
+ /**
+ * Returns an array of selected relationship names.
+ */
+ var getSelectedRelationships = function () {
+ // get all available relationships
+ var availableRelationships = $('#relationship-names');
+ var selectedRelationships = [];
+
+ // go through each relationship to determine which are selected
+ $.each(availableRelationships.children(), function (i, relationshipElement) {
+ var relationship = $(relationshipElement);
+
+ // get each relationship and its corresponding checkbox
+ var relationshipCheck = relationship.children('div.available-relationship');
+
+ // see if this relationship has been selected
+ if (relationshipCheck.hasClass('checkbox-checked')) {
+ selectedRelationships.push(relationship.children('span.relationship-name-value').text());
+ }
+ });
+
+ return selectedRelationships;
+ };
+
+ /**
+ * Validates the specified settings.
+ */
+ var validateSettings = function () {
+ var errors = [];
+
+ // validate the settings
+ if (nfCommon.isBlank($('#flow-file-expiration').val())) {
+ errors.push('File expiration must be specified');
+ }
+ if (!$.isNumeric($('#back-pressure-object-threshold').val())) {
+ errors.push('Back pressure object threshold must be an integer value');
+ }
+ if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) {
+ errors.push('Back pressure data size threshold must be specified');
+ }
+ if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE'
+ && nfCommon.isBlank($('#load-balance-partition-attribute').val())) {
+ errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"');
+ }
+
+ if (errors.length > 0) {
+ nfDialog.showOkDialog({
+ headerText: 'Connection Configuration',
+ dialogContent: nfCommon.formatUnorderedList(errors)
+ });
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Resets the dialog.
+ */
+ var resetDialog = function () {
+ // reset the prioritizers
+ var selectedList = $('#prioritizer-selected');
+ var availableList = $('#prioritizer-available');
+ selectedList.children().detach().appendTo(availableList);
+
+ // sort the available list
+ var listItems = availableList.children('li').get();
+ listItems.sort(function (a, b) {
+ var compA = $(a).text().toUpperCase();
+ var compB = $(b).text().toUpperCase();
+ return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
+ });
+
+ // clear the available list and re-insert each list item
+ $.each(listItems, function () {
+ $(this).detach();
+ });
+ $.each(listItems, function () {
+ $(this).appendTo(availableList);
+ });
+
+ // reset the fields
+ $('#connection-name').val('');
+ $('#relationship-names').css('border-width', '0').empty();
+ $('#relationship-names-container').show();
+
+ // clear the id field
+ nfCommon.clearField('connection-id');
+
+ // hide all the connection source panels
+ $('#processor-source').hide();
+ $('#input-port-source').hide();
+ $('#output-port-source').hide();
+ $('#read-only-output-port-source').hide();
+ $('#funnel-source').hide();
+
+ // hide all the connection destination panels
+ $('#processor-destination').hide();
+ $('#input-port-destination').hide();
+ $('#output-port-destination').hide();
+ $('#funnel-destination').hide();
+
+ // clear and destination details
+ $('#connection-source-id').val('');
+ $('#connection-source-component-id').val('');
+ $('#connection-source-group-id').val('');
+
+ // clear any destination details
+ $('#connection-destination-id').val('');
+ $('#connection-destination-component-id').val('');
+ $('#connection-destination-group-id').val('');
+
+ // clear any ports
+ $('#output-port-options').empty();
+ $('#input-port-options').empty();
+
+ // clear load balance settings
+ $('#load-balance-strategy-combo').combo('setSelectedOption', nfCommon.loadBalanceStrategyOptions[0]);
+ $('#load-balance-partition-attribute').val('');
+ $('#load-balance-compression-combo').combo('setSelectedOption', nfCommon.loadBalanceCompressionOptions[0]);
+
+ // see if the temp edge needs to be removed
+ removeTempEdge();
+ };
+
+ var nfConnectionConfiguration = {
+
+ /**
+ * Initialize the connection configuration.
+ *
+ * @param nfBirdseyeRef The nfBirdseye module.
+ * @param nfGraphRef The nfGraph module.
+ */
+ init: function (nfBirdseyeRef, nfGraphRef, defaultBackPressureObjectThresholdRef, defaultBackPressureDataSizeThresholdRef) {
+ nfBirdseye = nfBirdseyeRef;
+ nfGraph = nfGraphRef;
+
+ defaultBackPressureObjectThreshold = defaultBackPressureObjectThresholdRef;
+ defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThresholdRef;
+
+ // initially hide the relationship names container
+ $('#relationship-names-container').show();
+
+ // initialize the configure connection dialog
+ $('#connection-configuration').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Configure Connection',
+ handler: {
+ close: function () {
+ // reset the dialog on close
+ resetDialog();
+ },
+ open: function () {
+ nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
+ }
+ }
+ });
+
+ // initialize the properties tabs
+ $('#connection-configuration-tabs').tabbs({
+ tabStyle: 'tab',
+ selectedTabStyle: 'selected-tab',
+ scrollableTabContentStyle: 'scrollable',
+ tabs: [{
+ name: 'Details',
+ tabContentId: 'connection-details-tab-content'
+ }, {
+ name: 'Settings',
+ tabContentId: 'connection-settings-tab-content'
+ }]
+ });
+
+ // initialize the load balance strategy combo
+ $('#load-balance-strategy-combo').combo({
+ options: nfCommon.loadBalanceStrategyOptions,
+ select: function (selectedOption) {
+ // Show the appropriate configurations
+ if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') {
+ $('#load-balance-partition-attribute-setting-separator').show();
+ $('#load-balance-partition-attribute-setting').show();
+ } else {
+ $('#load-balance-partition-attribute-setting-separator').hide();
+ $('#load-balance-partition-attribute-setting').hide();
+ }
+ if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') {
+ $('#load-balance-compression-setting').hide();
+ } else {
+ $('#load-balance-compression-setting').show();
+ }
+ }
+ });
+
+
+ // initialize the load balance compression combo
+ $('#load-balance-compression-combo').combo({
+ options: nfCommon.loadBalanceCompressionOptions
+ });
+
+ // load the processor prioritizers
+ $.ajax({
+ type: 'GET',
+ url: config.urls.prioritizers,
+ dataType: 'json'
+ }).done(function (response) {
+ // create an element for each available prioritizer
+ $.each(response.prioritizerTypes, function (i, documentedType) {
+ nfConnectionConfiguration.addAvailablePrioritizer('#prioritizer-available', documentedType);
+ });
+
+ // make the prioritizer containers sortable
+ $('#prioritizer-available, #prioritizer-selected').sortable({
+ containment: $('#connection-settings-tab-content').find('.settings-right'),
+ connectWith: 'ul',
+ placeholder: 'ui-state-highlight',
+ scroll: true,
+ opacity: 0.6
+ });
+ $('#prioritizer-available, #prioritizer-selected').disableSelection();
+ }).fail(nfErrorHandler.handleAjaxError);
+ },
+
+ /**
+ * Adds the specified prioritizer to the specified container.
+ *
+ * @argument {string} prioritizerContainer The dom Id of the prioritizer container
+ * @argument {object} prioritizerType The type of prioritizer
+ */
+ addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) {
+ var type = prioritizerType.type;
+ var name = nfCommon.substringAfterLast(type, '.');
+
+ // add the prioritizers to the available list
+ var prioritizerList = $(prioritizerContainer);
+ var prioritizer = $('<li></li>').append($('<span style="float: left;"></span>').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList);
+
+ // add the description if applicable
+ if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) {
+ $('<div class="fa fa-question-circle"></div>').appendTo(prioritizer).qtip($.extend({
+ content: nfCommon.escapeHtml(prioritizerType.description)
+ }, nfCommon.config.tooltipConfig));
+ }
+ },
+
+ /**
+ * Shows the dialog for creating a new connection.
+ *
+ * @argument {string} sourceId The source id
+ * @argument {string} destinationId The destination id
+ */
+ createConnection: function (sourceId, destinationId) {
+ // select the source and destination
+ var source = d3.select('#id-' + sourceId);
+ var destination = d3.select('#id-' + destinationId);
+
+ if (source.empty() || destination.empty()) {
+ return;
+ }
+
+ // initialize the connection dialog
+ $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () {
+
+ if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
+ addDialogRelationshipsChangeListener();
+
+ // if there is a single relationship auto select
+ var relationships = $('#relationship-names').children('div');
+ if (relationships.length === 1) {
+ relationships.children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
+ }
+
+ // configure the button model
+ $('#connection-configuration').modal('setButtonModel', [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: function () {
+ // ensure some relationships were selected
+ return getSelectedRelationships().length === 0;
+ },
+ handler: {
+ click: function () {
+ addConnection(getSelectedRelationships());
+
+ // close the dialog
+ $('#connection-configuration').modal('hide');
+ }
+ }
+ },
+ {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $('#connection-configuration').modal('hide');
+ }
+ }
+ }]);
+ } else {
+ // configure the button model
+ $('#connection-configuration').modal('setButtonModel', [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ // add the connection
+ addConnection();
+
+ // close the dialog
+ $('#connection-configuration').modal('hide');
+ }
+ }
+ },
+ {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $('#connection-configuration').modal('hide');
+ }
+ }
+ }]);
+ }
+
+ // set the default values
+ $('#flow-file-expiration').val('0 sec');
+ $('#back-pressure-object-threshold').val(defaultBackPressureObjectThreshold);
+ $('#back-pressure-data-size-threshold').val(defaultBackPressureDataSizeThreshold);
+
+ // select the first tab
+ $('#connection-configuration-tabs').find('li:first').click();
+
+ // configure the header and show the dialog
+ $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show');
+
+ // add the ellipsis if necessary
+ $('#connection-configuration div.relationship-name').ellipsis();
+
+ // fill in the connection id
+ nfCommon.populateField('connection-id', null);
+
+ // show the border if necessary
+ var relationshipNames = $('#relationship-names');
+ if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
+ relationshipNames.css('border-width', '1px');
+ }
+ }).fail(function () {
+ // see if the temp edge needs to be removed
+ removeTempEdge();
+ });
+ },
+
+ /**
+ * Shows the configuration for the specified connection. If a destination is
+ * specified it will be considered a new destination.
+ *
+ * @argument {selection} selection The connection entry
+ * @argument {selection} destination Optional new destination
+ */
+ showConfiguration: function (selection, destination) {
+ return $.Deferred(function (deferred) {
+ var connectionEntry = selection.datum();
+ var connection = connectionEntry.component;
+
+ // identify the source component
+ var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry);
+ var source = d3.select('#id-' + sourceComponentId);
+
+ // identify the destination component
+ if (nfCommon.isUndefinedOrNull(destination)) {
+ var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry);
+ destination = d3.select('#id-' + destinationComponentId);
+ }
+
+ // initialize the connection dialog
+ $.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () {
+ var availableRelationships = connection.availableRelationships;
+ var selectedRelationships = connection.selectedRelationships;
+
+ // Added this block to force add destination relationships to
+ // get blueprint generation working
+ if (nfCanvasUtils.isProcessor(destination)) {
+ if (availableRelationships == undefined) {
+ // When the source is a port, this could be null or
+ // undefined since the backend the attribute doesn't
+ // exist
+ availableRelationships = [];
+ }
+
+ var processorData = destination.datum();
+ $.each(processorData.component.relationships, function (i, relationship) {
+ availableRelationships.push(relationship.name);
+ });
+ }
+
+ // show the available relationship if applicable
+ if (nfCommon.isDefinedAndNotNull(availableRelationships) || nfCommon.isDefinedAndNotNull(selectedRelationships)) {
+ // populate the available connections
+ $.each(availableRelationships, function (i, name) {
+ createRelationshipOption(name);
+ });
+
+ addDialogRelationshipsChangeListener();
+
+ // ensure all selected relationships are present
+ // (may be undefined) and selected
+ $.each(selectedRelationships, function (i, name) {
+ // mark undefined relationships accordingly
+ if ($.inArray(name, availableRelationships) === -1) {
+ var option = createRelationshipOption(name);
+ $(option).children('div.relationship-name').addClass('undefined');
+ }
+
+ // ensure all selected relationships are checked
+ var relationships = $('#relationship-names').children('div');
+ $.each(relationships, function (i, relationship) {
+ var relationshipName = $(relationship).children('span.relationship-name-value');
+ if (relationshipName.text() === name) {
+ $(relationship).children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
+ }
+ });
+ });
+ }
+
+ // if the source is a process group or remote process group, select the appropriate port if applicable
+ if (nfCanvasUtils.isProcessGroup(source) || nfCanvasUtils.isRemoteProcessGroup(source)) {
+ // populate the connection source details
+ $('#connection-source-id').val(connection.source.id);
+ $('#read-only-output-port-name').text(connection.source.name).attr('title', connection.source.name);
+ }
+
+ // if the destination is a process gorup or remote process group, select the appropriate port if applicable
+ if (nfCanvasUtils.isProcessGroup(destination) || nfCanvasUtils.isRemoteProcessGroup(destination)) {
+ var destinationData = destination.datum();
+
+ // when the group ids differ, its a new destination component so we don't want to preselect any port
+ if (connection.destination.groupId === destinationData.id) {
+ $('#input-port-options').combo('setSelectedOption', {
+ value: connection.destination.id
+ });
+ }
+ }
+
+ // set the connection settings
+ $('#connection-name').val(connection.name);
+ $('#flow-file-expiration').val(connection.flowFileExpiration);
+ $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold);
+ $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold);
+
+ // select the load balance combos
+ $('#load-balance-strategy-combo').combo('setSelectedOption', {
+ value: connection.loadBalanceStrategy
+ });
+ $('#load-balance-compression-combo').combo('setSelectedOption', {
+ value: connection.loadBalanceCompression
+ });
+ $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute);
+
+ // format the connection id
+ nfCommon.populateField('connection-id', connection.id);
+
+ // handle each prioritizer
+ $.each(connection.prioritizers, function (i, type) {
+ $('#prioritizer-available').children('li[id="' + type + '"]').detach().appendTo('#prioritizer-selected');
+ });
+
+ // store the connection details
+ $('#connection-uri').val(connectionEntry.uri);
+
+ // configure the button model
+ $('#connection-configuration').modal('setButtonModel', [{
+ buttonText: 'Apply',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: function () {
+ // ensure some relationships were selected with a processor as the source
+ if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
+ return getSelectedRelationships().length === 0;
+ }
+ return false;
+ },
+ handler: {
+ click: function () {
+ // see if we're working with a processor as the source
+ if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
+ // update the selected relationships
+ updateConnection(getSelectedRelationships()).done(function () {
+ deferred.resolve();
+ }).fail(function () {
+ deferred.reject();
+ });
+ } else {
+ // there are no relationships, but the source wasn't a processor, so update anyway
+ updateConnection(undefined).done(function () {
+ deferred.resolve();
+ }).fail(function () {
+ deferred.reject();
+ });
+ }
+
+ // close the dialog
+ $('#connection-configuration').modal('hide');
+ }
+ }
+ },
+ {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ // hide the dialog
+ $('#connection-configuration').modal('hide');
+
+ // reject the deferred
+ deferred.reject();
+ }
+ }
+ }]);
+
+ // show the details dialog
+ $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show');
+
+ // add the ellipsis if necessary
+ $('#connection-configuration div.relationship-name').ellipsis();
+
+ // show the border if necessary
+ var relationshipNames = $('#relationship-names');
+ if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
+ relationshipNames.css('border-width', '1px');
+ }
+ }).fail(function () {
+ deferred.reject();
+ });
+ }).promise();
+ }
+ };
+
+ return nfConnectionConfiguration;
+}));
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js
new file mode 100644
index 0000000..3c595ca
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js
@@ -0,0 +1,1990 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global define, module, require, exports */
+
+/**
+ * Handles versioning.
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'nf.ng.Bridge',
+ 'nf.ErrorHandler',
+ 'nf.Dialog',
+ 'nf.Storage',
+ 'nf.Common',
+ 'nf.Client',
+ 'nf.CanvasUtils',
+ 'nf.ProcessGroup',
+ 'nf.ProcessGroupConfiguration',
+ 'nf.Graph',
+ 'nf.Birdseye'],
+ function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
+ return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.FlowVerison =
+ factory(require('jquery'),
+ require('nf.ng.Bridge'),
+ require('nf.ErrorHandler'),
+ require('nf.Dialog'),
+ require('nf.Storage'),
+ require('nf.Common'),
+ require('nf.Client'),
+ require('nf.CanvasUtils'),
+ require('nf.ProcessGroup'),
+ require('nf.ProcessGroupConfiguration'),
+ require('nf.Graph'),
+ require('nf.Birdseye')));
+ } else {
+ nf.FlowVersion = factory(root.$,
+ root.nf.ng.Bridge,
+ root.nf.ErrorHandler,
+ root.nf.Dialog,
+ root.nf.Storage,
+ root.nf.Common,
+ root.nf.Client,
+ root.nf.CanvasUtils,
+ root.nf.ProcessGroup,
+ root.nf.ProcessGroupConfiguration,
+ root.nf.Graph,
+ root.nf.Birdseye);
+ }
+}(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
+ 'use strict';
+
+ var serverTimeOffset = null;
+
+ var gridOptions = {
+ forceFitColumns: true,
+ enableTextSelectionOnCells: true,
+ enableCellNavigation: true,
+ enableColumnReorder: false,
+ autoEdit: false,
+ multiSelect: false,
+ rowHeight: 24
+ };
+
+ /**
+ * Reset the save flow version dialog.
+ */
+ var resetSaveFlowVersionDialog = function () {
+ $('#save-flow-version-registry-combo').combo('destroy').hide();
+ $('#save-flow-version-bucket-combo').combo('destroy').hide();
+
+ $('#save-flow-version-label').text('');
+
+ $('#save-flow-version-registry').text('').hide();
+ $('#save-flow-version-bucket').text('').hide();
+
+ $('#save-flow-version-name').text('').hide();
+ $('#save-flow-version-description').removeClass('unset blank').text('').hide();
+
+ $('#save-flow-version-name-field').val('').hide();
+ $('#save-flow-version-description-field').val('').hide();
+ $('#save-flow-version-change-comments').val('');
+
+ $('#save-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
+ };
+
+ /**
+ * Reset the revert local changes dialog.
+ */
+ var resetRevertLocalChangesDialog = function () {
+ $('#revert-local-changes-process-group-id').text('');
+
+ clearLocalChangesGrid($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
+ };
+
+ /**
+ * Reset the show local changes dialog.
+ */
+ var resetShowLocalChangesDialog = function () {
+ clearLocalChangesGrid($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
+ };
+
+ /**
+ * Clears the local changes grid.
+ */
+ var clearLocalChangesGrid = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
+ var localChangesGrid = localChangesTable.data('gridInstance');
+ if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
+ localChangesGrid.setSelectedRows([]);
+ localChangesGrid.resetActiveCell();
+
+ var localChangesData = localChangesGrid.getData();
+ localChangesData.setItems([]);
+ localChangesData.setFilterArgs({
+ searchString: ''
+ });
+ }
+
+ filterInput.val('');
+
+ displayedLabel.text('0');
+ totalLabel.text('0');
+ };
+
+ /**
+ * Clears the version grid
+ */
+ var clearFlowVersionsGrid = function () {
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
+ importFlowVersionGrid.setSelectedRows([]);
+ importFlowVersionGrid.resetActiveCell();
+
+ var importFlowVersionData = importFlowVersionGrid.getData();
+ importFlowVersionData.setItems([]);
+ }
+ };
+
+ /**
+ * Reset the import flow version dialog.
+ */
+ var resetImportFlowVersionDialog = function () {
+ $('#import-flow-version-dialog').removeData('pt');
+
+ $('#import-flow-version-registry-combo').combo('destroy').hide();
+ $('#import-flow-version-bucket-combo').combo('destroy').hide();
+ $('#import-flow-version-name-combo').combo('destroy').hide();
+
+ $('#import-flow-version-registry').text('').hide();
+ $('#import-flow-version-bucket').text('').hide();
+ $('#import-flow-version-name').text('').hide();
+
+ clearFlowVersionsGrid();
+
+ $('#import-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
+
+ $('#import-flow-version-container').hide();
+ $('#import-flow-version-label').text('');
+ };
+
+ /**
+ * Loads the registries into the specified registry combo.
+ *
+ * @param dialog
+ * @param registryCombo
+ * @param bucketCombo
+ * @param flowCombo
+ * @param selectBucket
+ * @param bucketCheck
+ * @returns {deferred}
+ */
+ var loadRegistries = function (dialog, registryCombo, bucketCombo, flowCombo, selectBucket, bucketCheck) {
+ return $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/registries',
+ dataType: 'json'
+ }).done(function (registriesResponse) {
+ var registries = [];
+
+ if (nfCommon.isDefinedAndNotNull(registriesResponse.registries) && registriesResponse.registries.length > 0) {
+ registriesResponse.registries.sort(function (a, b) {
+ return a.registry.name > b.registry.name;
+ });
+
+ $.each(registriesResponse.registries, function (_, registryEntity) {
+ var registry = registryEntity.registry;
+ registries.push({
+ text: registry.name,
+ value: registry.id,
+ description: nfCommon.escapeHtml(registry.description)
+ });
+ });
+ } else {
+ registries.push({
+ text: 'No available registries',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ });
+ }
+
+ // load the registries
+ registryCombo.combo({
+ options: registries,
+ select: function (selectedOption) {
+ selectRegistry(dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck)
+ }
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Loads the buckets for the specified registryIdentifier for the current user.
+ *
+ * @param registryIdentifier
+ * @param bucketCombo
+ * @param flowCombo
+ * @param selectBucket
+ * @param bucketCheck
+ * @returns {*}
+ */
+ var loadBuckets = function (registryIdentifier, bucketCombo, flowCombo, selectBucket, bucketCheck) {
+ return $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets',
+ dataType: 'json'
+ }).done(function (response) {
+ var buckets = [];
+
+ if (nfCommon.isDefinedAndNotNull(response.buckets) && response.buckets.length > 0) {
+ response.buckets.sort(function (a, b) {
+ if (a.permissions.canRead === false && b.permissions.canRead === false) {
+ return 0;
+ } else if (a.permissions.canRead === false) {
+ return -1;
+ } else if (b.permissions.canRead === false) {
+ return 1;
+ }
+
+ return a.bucket.name > b.bucket.name;
+ });
+
+ $.each(response.buckets, function (_, bucketEntity) {
+ if (bucketEntity.permissions.canRead === true) {
+ var bucket = bucketEntity.bucket;
+
+ if (bucketCheck(bucketEntity)) {
+ buckets.push({
+ text: bucket.name,
+ value: bucket.id,
+ description: nfCommon.escapeHtml(bucket.description)
+ });
+ }
+ }
+ });
+ }
+
+ if (buckets.length === 0) {
+ buckets.push({
+ text: 'No available buckets',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ });
+
+ if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+ flowCombo.combo('destroy').combo({
+ options: [{
+ text: 'No available flows',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+ }
+ }
+
+ // load the buckets
+ bucketCombo.combo('destroy').combo({
+ options: buckets,
+ select: selectBucket
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Select handler for the registries combo.
+ *
+ * @param dialog
+ * @param selectedOption
+ * @param bucketCombo
+ * @param flowCombo
+ * @param selectBucket
+ * @param bucketCheck
+ */
+ var selectRegistry = function (dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck) {
+ var showNoBucketsAvailable = function () {
+ bucketCombo.combo('destroy').combo({
+ options: [{
+ text: 'No available buckets',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+
+ if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+ flowCombo.combo('destroy').combo({
+ options: [{
+ text: 'No available flows',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+ }
+
+ dialog.modal('refreshButtons');
+ };
+
+ if (selectedOption.disabled === true) {
+ showNoBucketsAvailable();
+ } else {
+ bucketCombo.combo('destroy').combo({
+ options: [{
+ text: 'Loading buckets...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+
+ if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+ flowCombo.combo('destroy').combo({
+ options: [{
+ text: 'Loading flows...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+
+ clearFlowVersionsGrid();
+ }
+
+ loadBuckets(selectedOption.value, bucketCombo, flowCombo, selectBucket, bucketCheck).fail(function () {
+ showNoBucketsAvailable();
+ });
+ }
+ };
+
+ /**
+ * Select handler for the buckets combo.
+ *
+ * @param selectedOption
+ */
+ var selectBucketSaveFlowVersion = function (selectedOption) {
+ $('#save-flow-version-dialog').modal('refreshButtons');
+ };
+
+ /**
+ * Saves a flow version.
+ * @author: Renu
+ * @desc for lines 390-396: when a dflow is committed, then environment dropdown selection is enabled.
+ * If Env is pre-selected and enabled =>submit button is enabled
+ * @returns {*}
+ */
+ var saveFlowVersion = function () {
+ var processGroupId = $('#save-flow-version-process-group-id').text();
+ var processGroupRevision = $('#save-flow-version-process-group-id').data('revision');
+
+ $('#environmentType').prop('disabled', false);
+ console.log("test submit btn..... ");
+
+ if($('#environmentType').val() && !$('#environmentType').prop("disabled")){
+ $('#operate-submit-btn').prop('disabled', false);
+ console.log("button is enabled bcz env is already selected and not disabled");
+ }
+
+ var saveFlowVersionRequest = {
+ processGroupRevision: nfClient.getRevision({
+ revision: {
+ version: processGroupRevision.version
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+ };
+
+ var versionControlInformation = $('#save-flow-version-process-group-id').data('versionControlInformation');
+ if (nfCommon.isDefinedAndNotNull(versionControlInformation)) {
+ saveFlowVersionRequest['versionedFlow'] = {
+ registryId: versionControlInformation.registryId,
+ bucketId: versionControlInformation.bucketId,
+ flowId: versionControlInformation.flowId,
+ comments: $('#save-flow-version-change-comments').val()
+ }
+ } else {
+ var selectedRegistry = $('#save-flow-version-registry-combo').combo('getSelectedOption');
+ var selectedBucket = $('#save-flow-version-bucket-combo').combo('getSelectedOption');
+
+ saveFlowVersionRequest['versionedFlow'] = {
+ registryId: selectedRegistry.value,
+ bucketId: selectedBucket.value,
+ flowName: $('#save-flow-version-name-field').val(),
+ description: $('#save-flow-version-description-field').val(),
+ comments: $('#save-flow-version-change-comments').val()
+ };
+ }
+
+ return $.ajax({
+ type: 'POST',
+ data: JSON.stringify(saveFlowVersionRequest),
+ url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).fail(nfErrorHandler.handleAjaxError)
+
+
+ };
+
+ /**
+ * Sorts the specified data using the specified sort details.
+ *
+ * @param {object} sortDetails
+ * @param {object} data
+ */
+ var sort = function (sortDetails, data) {
+ // defines a function for sorting
+ var comparer = function (a, b) {
+ var aIsBlank = nfCommon.isBlank(a[sortDetails.columnId]);
+ var bIsBlank = nfCommon.isBlank(b[sortDetails.columnId]);
+
+ if (aIsBlank && bIsBlank) {
+ return 0;
+ } else if (aIsBlank) {
+ return 1;
+ } else if (bIsBlank) {
+ return -1;
+ }
+
+ return a[sortDetails.columnId] === b[sortDetails.columnId] ? 0 : a[sortDetails.columnId] > b[sortDetails.columnId] ? 1 : -1;
+ };
+
+ // perform the sort
+ data.sort(comparer, sortDetails.sortAsc);
+ };
+
+ var initImportFlowVersionTable = function () {
+ var importFlowVersionTable = $('#import-flow-version-table');
+
+ var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+ return nfCommon.escapeHtml(value);
+ };
+
+ var timestampFormatter = function (row, cell, value, columnDef, dataContext) {
+ // get the current user time to properly convert the server time
+ var now = new Date();
+
+ // convert the user offset to millis
+ var userTimeOffset = now.getTimezoneOffset() * 60 * 1000;
+
+ // create the proper date by adjusting by the offsets
+ var date = new Date(dataContext.timestamp + userTimeOffset + serverTimeOffset);
+ return nfCommon.formatDateTime(date);
+ };
+
+ // define the column model for flow versions
+ var importFlowVersionColumns = [
+ {
+ id: 'version',
+ name: 'Version',
+ field: 'version',
+ formatter: valueFormatter,
+ sortable: true,
+ resizable: true,
+ width: 75,
+ maxWidth: 75
+ },
+ {
+ id: 'timestamp',
+ name: 'Created',
+ field: 'timestamp',
+ formatter: timestampFormatter,
+ sortable: true,
+ resizable: true,
+ width: 175,
+ maxWidth: 175
+ },
+ {
+ id: 'changeComments',
+ name: 'Comments',
+ field: 'comments',
+ sortable: true,
+ resizable: true,
+ formatter: valueFormatter
+ }
+ ];
+
+ // initialize the dataview
+ var importFlowVersionData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+
+ // initialize the sort
+ sort({
+ columnId: 'version',
+ sortAsc: false
+ }, importFlowVersionData);
+
+ // initialize the grid
+ var importFlowVersionGrid = new Slick.Grid(importFlowVersionTable, importFlowVersionData, importFlowVersionColumns, gridOptions);
+ importFlowVersionGrid.setSelectionModel(new Slick.RowSelectionModel());
+ importFlowVersionGrid.registerPlugin(new Slick.AutoTooltips());
+ importFlowVersionGrid.setSortColumn('version', false);
+ importFlowVersionGrid.onSort.subscribe(function (e, args) {
+ sort({
+ columnId: args.sortCol.id,
+ sortAsc: args.sortAsc
+ }, importFlowVersionData);
+ });
+ importFlowVersionGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+ $('#import-flow-version-dialog').modal('refreshButtons');
+ });
+ importFlowVersionGrid.onDblClick.subscribe(function (e, args) {
+ if ($('#import-flow-version-label').is(':visible')) {
+ changeFlowVersion();
+ } else {
+ importFlowVersion().always(function () {
+ $('#import-flow-version-dialog').modal('hide');
+ });
+ }
+ });
+
+ // wire up the dataview to the grid
+ importFlowVersionData.onRowCountChanged.subscribe(function (e, args) {
+ importFlowVersionGrid.updateRowCount();
+ importFlowVersionGrid.render();
+ });
+ importFlowVersionData.onRowsChanged.subscribe(function (e, args) {
+ importFlowVersionGrid.invalidateRows(args.rows);
+ importFlowVersionGrid.render();
+ });
+ importFlowVersionData.syncGridSelection(importFlowVersionGrid, true);
+
+ // hold onto an instance of the grid
+ importFlowVersionTable.data('gridInstance', importFlowVersionGrid);
+ };
+
+ /**
+ * Initializes the specified local changes table.
+ *
+ * @param localChangesTable
+ * @param filterInput
+ * @param displayedLabel
+ * @param totalLabel
+ */
+ var initLocalChangesTable = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
+
+ var getFilterText = function () {
+ return filterInput.val();
+ };
+
+ var applyFilter = function () {
+ // get the dataview
+ var localChangesGrid = localChangesTable.data('gridInstance');
+
+ // ensure the grid has been initialized
+ if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
+ var localChangesData = localChangesGrid.getData();
+
+ // update the search criteria
+ localChangesData.setFilterArgs({
+ searchString: getFilterText()
+ });
+ localChangesData.refresh();
+ }
+ };
+
+ var filter = function (item, args) {
+ if (args.searchString === '') {
+ return true;
+ }
+
+ try {
+ // perform the row filtering
+ var filterExp = new RegExp(args.searchString, 'i');
+ } catch (e) {
+ // invalid regex
+ return false;
+ }
+
+ // determine if the item matches the filter
+ var matchesId = item['componentId'].search(filterExp) >= 0;
+ var matchesDifferenceType = item['differenceType'].search(filterExp) >= 0;
+ var matchesDifference = item['difference'].search(filterExp) >= 0;
+
+ // conditionally consider the component name
+ var matchesComponentName = false;
+ if (nfCommon.isDefinedAndNotNull(item['componentName'])) {
+ matchesComponentName = item['componentName'].search(filterExp) >= 0;
+ }
+
+ return matchesId || matchesComponentName || matchesDifferenceType || matchesDifference;
+ };
+
+ // initialize the component state filter
+ filterInput.on('keyup', function () {
+ applyFilter();
+ });
+
+ var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+ return nfCommon.escapeHtml(value);
+ };
+
+ var actionsFormatter = function (row, cell, value, columnDef, dataContext) {
+ var markup = '';
+
+ if (dataContext.differenceType !== 'Component Removed' && nfCommon.isDefinedAndNotNull(dataContext.processGroupId)) {
+ markup += '<div class="pointer go-to-component fa fa-long-arrow-right" title="Go To"></div>';
+ }
+
+ return markup;
+ };
+
+ // define the column model for local changes
+ var localChangesColumns = [
+ {
+ id: 'componentName',
+ name: 'Component Name',
+ field: 'componentName',
+ formatter: valueFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'differenceType',
+ name: 'Change Type',
+ field: 'differenceType',
+ formatter: valueFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'difference',
+ name: 'Difference',
+ field: 'difference',
+ formatter: valueFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'actions',
+ name: '&nbsp;',
+ formatter: actionsFormatter,
+ sortable: false,
+ resizable: false,
+ width: 25
+ }
+ ];
+
+ // initialize the dataview
+ var localChangesData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ localChangesData.setFilterArgs({
+ searchString: getFilterText()
+ });
+ localChangesData.setFilter(filter);
+
+ // initialize the sort
+ sort({
+ columnId: 'componentName',
+ sortAsc: true
+ }, localChangesData);
+
+ // initialize the grid
+ var localChangesGrid = new Slick.Grid(localChangesTable, localChangesData, localChangesColumns, gridOptions);
+ localChangesGrid.setSelectionModel(new Slick.RowSelectionModel());
+ localChangesGrid.registerPlugin(new Slick.AutoTooltips());
+ localChangesGrid.setSortColumn('componentName', true);
+ localChangesGrid.onSort.subscribe(function (e, args) {
+ sort({
+ columnId: args.sortCol.id,
+ sortAsc: args.sortAsc
+ }, localChangesData);
+ });
+
+ // configure a click listener
+ localChangesGrid.onClick.subscribe(function (e, args) {
+ var target = $(e.target);
+
+ // get the node at this row
+ var componentDifference = localChangesData.getItem(args.row);
+
+ // determine the desired action
+ if (localChangesGrid.getColumns()[args.cell].id === 'actions') {
+ if (target.hasClass('go-to-component')) {
+ if (componentDifference.componentType === 'Controller Service') {
+ nfProcessGroupConfiguration.showConfiguration(componentDifference.processGroupId).done(function () {
+ nfProcessGroupConfiguration.selectControllerService(componentDifference.componentId);
+
+ localChangesTable.closest('.large-dialog').modal('hide');
+ });
+ } else {
+ nfCanvasUtils.showComponent(componentDifference.processGroupId, componentDifference.componentId).done(function () {
+ localChangesTable.closest('.large-dialog').modal('hide');
+ });
+ }
+ }
+ }
+ });
+
+ // wire up the dataview to the grid
+ localChangesData.onRowCountChanged.subscribe(function (e, args) {
+ localChangesGrid.updateRowCount();
+ localChangesGrid.render();
+
+ // update the total number of displayed items
+ displayedLabel.text(nfCommon.formatInteger(args.current));
+ });
+ localChangesData.onRowsChanged.subscribe(function (e, args) {
+ localChangesGrid.invalidateRows(args.rows);
+ localChangesGrid.render();
+ });
+ localChangesData.syncGridSelection(localChangesGrid, true);
+
+ // hold onto an instance of the grid
+ localChangesTable.data('gridInstance', localChangesGrid);
+
+ // initialize the number of display items
+ displayedLabel.text('0');
+ totalLabel.text('0');
+ };
+
+ /**
+ * Shows the import flow version dialog.
+ */
+ var showImportFlowVersionDialog = function () {
+ var pt = $('#new-process-group-dialog').data('pt');
+ $('#import-flow-version-dialog').data('pt', pt);
+
+ // update the registry and bucket visibility
+ var registryCombo = $('#import-flow-version-registry-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading registries...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ }).show();
+ var bucketCombo = $('#import-flow-version-bucket-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading buckets...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ }).show();
+ var flowCombo = $('#import-flow-version-name-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading flows...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ }).show();
+
+ loadRegistries($('#import-flow-version-dialog'), registryCombo, bucketCombo, flowCombo, selectBucketImportVersion, function (bucketEntity) {
+ return true;
+ }).done(function () {
+ // show the import dialog
+ $('#import-flow-version-dialog').modal('setHeaderText', 'Import Version').modal('setButtonModel', [{
+ buttonText: 'Import',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: disableImportOrChangeButton,
+ handler: {
+ click: function () {
+ importFlowVersion().always(function () {
+ $('#import-flow-version-dialog').modal('hide');
+ });
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+
+ // hide the new process group dialog
+ $('#new-process-group-dialog').modal('hide');
+ });
+ };
+
+ /**
+ * Loads the flow versions for the specified registry, bucket, and flow.
+ *
+ * @param registryIdentifier
+ * @param bucketIdentifier
+ * @param flowIdentifier
+ * @returns deferred
+ */
+ var loadFlowVersions = function (registryIdentifier, bucketIdentifier, flowIdentifier) {
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ var importFlowVersionData = importFlowVersionGrid.getData();
+
+ // begin the update
+ importFlowVersionData.beginUpdate();
+
+ // remove the current versions
+ importFlowVersionGrid.setSelectedRows([]);
+ importFlowVersionGrid.resetActiveCell();
+ importFlowVersionData.setItems([]);
+
+ return $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows/' + encodeURIComponent(flowIdentifier) + '/versions',
+ dataType: 'json'
+ }).done(function (response) {
+ if (nfCommon.isDefinedAndNotNull(response.versionedFlowSnapshotMetadataSet) && response.versionedFlowSnapshotMetadataSet.length > 0) {
+ $.each(response.versionedFlowSnapshotMetadataSet, function (_, entity) {
+ importFlowVersionData.addItem($.extend({
+ id: entity.versionedFlowSnapshotMetadata.version
+ }, entity.versionedFlowSnapshotMetadata));
+ });
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Flow Versions',
+ dialogContent: 'This flow does not have any versions available.'
+ });
+ }
+ }).fail(nfErrorHandler.handleAjaxError).always(function () {
+ // end the update
+ importFlowVersionData.endUpdate();
+
+ // resort
+ importFlowVersionData.reSort();
+ importFlowVersionGrid.invalidate();
+ });
+ };
+
+ /**
+ * Loads the versioned flows from the specified registry and bucket.
+ *
+ * @param registryIdentifier
+ * @param bucketIdentifier
+ * @param selectFlow
+ * @returns deferred
+ */
+ var loadFlows = function (registryIdentifier, bucketIdentifier, selectFlow) {
+ return $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows',
+ dataType: 'json'
+ }).done(function (response) {
+ var versionedFlows = [];
+
+ if (nfCommon.isDefinedAndNotNull(response.versionedFlows) && response.versionedFlows.length > 0) {
+ response.versionedFlows.sort(function (a, b) {
+ return a.versionedFlow.flowName > b.versionedFlow.flowName;
+ });
+
+ $.each(response.versionedFlows, function (_, versionedFlowEntity) {
+ var versionedFlow = versionedFlowEntity.versionedFlow;
+ versionedFlows.push({
+ text: versionedFlow.flowName,
+ value: versionedFlow.flowId,
+ description: nfCommon.escapeHtml(versionedFlow.description)
+ });
+ });
+ } else {
+ versionedFlows.push({
+ text: 'No available flows',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ });
+ }
+
+ // load the buckets
+ $('#import-flow-version-name-combo').combo('destroy').combo({
+ options: versionedFlows,
+ select: function (selectedFlow) {
+ if (nfCommon.isDefinedAndNotNull(selectedFlow.value)) {
+ selectFlow(registryIdentifier, bucketIdentifier, selectedFlow.value)
+ } else {
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ var importFlowVersionData = importFlowVersionGrid.getData();
+
+ // clear the current values
+ importFlowVersionData.beginUpdate();
+ importFlowVersionData.setItems([]);
+ importFlowVersionData.endUpdate();
+ }
+ }
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Handler when a versioned flow is selected.
+ *
+ * @param registryIdentifier
+ * @param bucketIdentifier
+ * @param flowIdentifier
+ */
+ var selectVersionedFlow = function (registryIdentifier, bucketIdentifier, flowIdentifier) {
+ loadFlowVersions(registryIdentifier, bucketIdentifier, flowIdentifier).done(function () {
+ $('#import-flow-version-dialog').modal('refreshButtons');
+ });
+ };
+
+ /**
+ * Handler when a bucket is selected.
+ *
+ * @param selectedBucket
+ */
+ var selectBucketImportVersion = function (selectedBucket) {
+ // clear the flow versions grid
+ clearFlowVersionsGrid();
+
+ if (nfCommon.isDefinedAndNotNull(selectedBucket.value)) {
+ // mark the flows as loading
+ $('#import-flow-version-name-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading flows...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+
+ var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption');
+
+ // load the flows for the currently selected registry and bucket
+ loadFlows(selectedRegistry.value, selectedBucket.value, selectVersionedFlow);
+ } else {
+ // mark no flows available
+ $('#import-flow-version-name-combo').combo('destroy').combo({
+ options: [{
+ text: 'No available flows',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ });
+ }
+ };
+
+ /**
+ * Imports the selected flow version.
+ */
+ var importFlowVersion = function () {
+ var pt = $('#import-flow-version-dialog').data('pt');
+
+ var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption');
+ var selectedBucket = $('#import-flow-version-bucket-combo').combo('getSelectedOption');
+ var selectedFlow = $('#import-flow-version-name-combo').combo('getSelectedOption');
+
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ var selectedVersionIndex = importFlowVersionGrid.getSelectedRows();
+ var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]);
+
+ var processGroupEntity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': 0
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'position': {
+ 'x': pt.x,
+ 'y': pt.y
+ },
+ 'versionControlInformation': {
+ 'registryId': selectedRegistry.value,
+ 'bucketId': selectedBucket.value,
+ 'flowId': selectedFlow.value,
+ 'version': selectedVersion.version
+ }
+ }
+ };
+
+ return $.ajax({
+ type: 'POST',
+ data: JSON.stringify(processGroupEntity),
+ url: '../nifi-api/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/process-groups',
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ // add the process group to the graph
+ nfGraph.add({
+ 'processGroups': [response]
+ }, {
+ 'selectAll': true
+ });
+
+ // update component visibility
+ nfGraph.updateVisibility();
+
+ // update the birdseye
+ nfBirdseye.refresh();
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Determines whether the import/change button is disabled.
+ *
+ * @returns {boolean}
+ */
+ var disableImportOrChangeButton = function () {
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
+ var selected = importFlowVersionGrid.getSelectedRows();
+
+ // if the version label is visible, this is a change version request so disable when
+ // the version that represents the current version is selected
+ if ($('#import-flow-version-label').is(':visible')) {
+ if (selected.length === 1) {
+ var selectedFlow = importFlowVersionGrid.getDataItem(selected[0]);
+
+ var currentVersion = parseInt($('#import-flow-version-label').text(), 10);
+ return currentVersion === selectedFlow.version;
+ } else {
+ return true;
+ }
+ } else {
+ // if importing, enable when a single row is selecting
+ return selected.length !== 1;
+ }
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Changes the flow version for the currently selected Process Group.
+ *
+ * @returns {deferred}
+ */
+ var changeFlowVersion = function () {
+ var changeTimer = null;
+ var changeRequest = null;
+ var cancelled = false;
+
+ var processGroupId = $('#import-flow-version-process-group-id').text();
+ var processGroupRevision = $('#import-flow-version-process-group-id').data('revision');
+ var versionControlInformation = $('#import-flow-version-process-group-id').data('versionControlInformation');
+
+ var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+ var selectedVersionIndex = importFlowVersionGrid.getSelectedRows();
+ var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]);
+
+ // update the button model of the change version status dialog
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Stop',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ cancelled = true;
+
+ $('#change-version-status-dialog').modal('setButtonModel', []);
+
+ // we are waiting for the next poll attempt
+ if (changeTimer !== null) {
+ // cancel it
+ clearTimeout(changeTimer);
+
+ // cancel the change request
+ completeChangeRequest();
+ }
+ }
+ }
+ }]);
+
+ // hide the import dialog immediately
+ $('#import-flow-version-dialog').modal('hide');
+
+ var submitChangeRequest = function () {
+ var changeVersionRequest = {
+ 'processGroupRevision': nfClient.getRevision({
+ 'revision': {
+ 'version': processGroupRevision.version
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'versionControlInformation': {
+ 'groupId': processGroupId,
+ 'registryId': versionControlInformation.registryId,
+ 'bucketId': versionControlInformation.bucketId,
+ 'flowId': versionControlInformation.flowId,
+ 'version': selectedVersion.version
+ }
+ };
+
+ return $.ajax({
+ type: 'POST',
+ data: JSON.stringify(changeVersionRequest),
+ url: '../nifi-api/versions/update-requests/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function () {
+ // initialize the progress bar value
+ updateProgress(0);
+
+ // show the progress dialog
+ $('#change-version-status-dialog').modal('show');
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ var pollChangeRequest = function () {
+ getChangeRequest().done(processChangeResponse);
+ };
+
+ var getChangeRequest = function () {
+ return $.ajax({
+ type: 'GET',
+ url: changeRequest.uri,
+ dataType: 'json'
+ }).fail(completeChangeRequest).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ var completeChangeRequest = function () {
+ if (cancelled === true) {
+ // update the message to indicate successful completion
+ $('#change-version-status-message').text('The change version request has been cancelled.');
+
+ // update the button model
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Close',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]);
+ }
+
+ if (nfCommon.isDefinedAndNotNull(changeRequest)) {
+ $.ajax({
+ type: 'DELETE',
+ url: changeRequest.uri + '?' + $.param({
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+ }),
+ dataType: 'json'
+ }).done(function (response) {
+ changeRequest = response.request;
+
+ // update the component that was changing
+ updateProcessGroup(processGroupId);
+
+ if (nfCommon.isDefinedAndNotNull(changeRequest.failureReason)) {
+ // hide the progress dialog
+ $('#change-version-status-dialog').modal('hide');
+
+ nfDialog.showOkDialog({
+ headerText: 'Change Version',
+ dialogContent: nfCommon.escapeHtml(changeRequest.failureReason)
+ });
+ } else {
+ // update the percent complete
+ updateProgress(changeRequest.percentCompleted);
+
+ // update the message to indicate successful completion
+ $('#change-version-status-message').text('This Process Group version has changed.');
+
+ // update the button model
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Close',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]);
+ }
+ });
+ }
+ };
+
+ var processChangeResponse = function (response) {
+ changeRequest = response.request;
+
+ if (changeRequest.complete === true || cancelled === true) {
+ completeChangeRequest();
+ } else {
+ // update the percent complete
+ updateProgress(changeRequest.percentCompleted);
+
+ // update the status of the listing request
+ $('#change-version-status-message').text(changeRequest.state);
+
+ changeTimer = setTimeout(function () {
+ // clear the timer since we've been invoked
+ changeTimer = null;
+
+ // poll revert request
+ pollChangeRequest();
+ }, 2000);
+ }
+ };
+
+ submitChangeRequest().done(processChangeResponse);
+ };
+
+ /**
+ * Gets the version control information for the specified process group id.
+ *
+ * @param processGroupId
+ * @return {deferred}
+ */
+ var getVersionControlInformation = function (processGroupId) {
+ return $.Deferred(function (deferred) {
+ if (processGroupId === nfCanvasUtils.getGroupId()) {
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json'
+ }).done(function (response) {
+ deferred.resolve(response);
+ }).fail(function () {
+ deferred.reject();
+ });
+ } else {
+ var processGroup = nfProcessGroup.get(processGroupId);
+ if (processGroup.permissions.canRead === true && processGroup.permissions.canWrite === true) {
+ deferred.resolve({
+ 'processGroupRevision': processGroup.revision,
+ 'versionControlInformation': processGroup.component.versionControlInformation
+ });
+ } else {
+ deferred.reject();
+ }
+ }
+ }).promise();
+ };
+
+ /**
+ * Updates the specified process group with the specified version control information.
+ *
+ * @param processGroupId
+ * @param versionControlInformation
+ */
+ var updateVersionControlInformation = function (processGroupId, versionControlInformation) {
+ // refresh either selected PG or bread crumb to reflect connected/tracking status
+ if (nfCanvasUtils.getGroupId() === processGroupId) {
+ nfNgBridge.injector.get('breadcrumbsCtrl').updateVersionControlInformation(processGroupId, versionControlInformation);
+ nfNgBridge.digest();
+ } else {
+ nfProcessGroup.reload(processGroupId);
+ }
+ };
+
+ /**
+ * Updates the specified process group following an operation that may change it's contents.
+ *
+ * @param processGroupId
+ */
+ var updateProcessGroup = function (processGroupId) {
+ if (nfCanvasUtils.getGroupId() === processGroupId) {
+ // if reverting/changing current PG... reload/refresh this group/canvas
+
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json'
+ }).done(function (response) {
+ // update the graph components
+ nfGraph.set(response.processGroupFlow.flow);
+
+ // update the component visibility
+ nfGraph.updateVisibility();
+
+ // update the breadcrumbs
+ var breadcrumbsCtrl = nfNgBridge.injector.get('breadcrumbsCtrl');
+ breadcrumbsCtrl.resetBreadcrumbs();
+ breadcrumbsCtrl.generateBreadcrumbs(response.processGroupFlow.breadcrumb);
+
+ // inform Angular app values have changed
+ nfNgBridge.digest();
+ }).fail(nfErrorHandler.handleAjaxError);
+ } else {
+ // if reverting selected PG... reload selected PG to update counts, etc
+ nfProcessGroup.reload(processGroupId);
+ }
+ };
+
+ /**
+ * Updates the progress bar to the specified percent complete.
+ *
+ * @param percentComplete
+ */
+ var updateProgress = function (percentComplete) {
+ // remove existing labels
+ var progressBar = $('#change-version-percent-complete');
+ progressBar.find('div.progress-label').remove();
+ progressBar.find('md-progress-linear').remove();
+
+ // update the progress
+ var label = $('<div class="progress-label"></div>').text(percentComplete + '%');
+ (nfNgBridge.injector.get('$compile')($('<md-progress-linear ng-cloak ng-value="' + percentComplete + '" class="md-hue-2" md-mode="determinate" aria-label="Searching Queue"></md-progress-linear>'))(nfNgBridge.rootScope)).appendTo(progressBar);
+ progressBar.append(label);
+ };
+
+ /**
+ * Shows local changes for the specified process group.
+ *
+ * @param processGroupId
+ * @param localChangesMessage
+ * @param localChangesTable
+ * @param totalLabel
+ */
+ var loadLocalChanges = function (processGroupId, localChangesMessage, localChangesTable, totalLabel) {
+ var localChangesGrid = localChangesTable.data('gridInstance');
+ var localChangesData = localChangesGrid.getData();
+
+ // begin the update
+ localChangesData.beginUpdate();
+
+ // remove the current versions
+ localChangesGrid.setSelectedRows([]);
+ localChangesGrid.resetActiveCell();
+ localChangesData.setItems([]);
+
+ // load the necessary details
+ var loadMessage = getVersionControlInformation(processGroupId).done(function (response) {
+ if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
+ var vci = response.versionControlInformation;
+ localChangesMessage.text('The following changes have been made to ' + vci.flowName + ' (Version ' + vci.version + ').');
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Change Version',
+ dialogContent: 'This Process Group is not currently under version control.'
+ });
+ }
+ });
+ var loadChanges = $.ajax({
+ type: 'GET',
+ url: '../nifi-api/process-groups/' + encodeURIComponent(processGroupId) + '/local-modifications',
+ dataType: 'json'
+ }).done(function (response) {
+ if (nfCommon.isDefinedAndNotNull(response.componentDifferences) && response.componentDifferences.length > 0) {
+ var totalDifferences = 0;
+ $.each(response.componentDifferences, function (_, componentDifference) {
+ $.each(componentDifference.differences, function (_, difference) {
+ localChangesData.addItem({
+ id: totalDifferences++,
+ componentId: componentDifference.componentId,
+ componentName: componentDifference.componentName,
+ componentType: componentDifference.componentType,
+ processGroupId: componentDifference.processGroupId,
+ differenceType: difference.differenceType,
+ difference: difference.difference
+ });
+ });
+ });
+
+ // end the update
+ localChangesData.endUpdate();
+
+ // resort
+ localChangesData.reSort();
+ localChangesGrid.invalidate();
+
+ // update the total displayed
+ totalLabel.text(nfCommon.formatInteger(totalDifferences));
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Local Changes',
+ dialogContent: 'This Process Group does not have any local changes.'
+ });
+ }
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ return $.when(loadMessage, loadChanges);
+ };
+
+ /**
+ * Revert local changes for the specified process group.
+ *
+ * @param processGroupId
+ */
+ var revertLocalChanges = function (processGroupId) {
+ getVersionControlInformation(processGroupId).done(function (response) {
+ if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
+ var revertTimer = null;
+ var revertRequest = null;
+ var cancelled = false;
+
+ // update the button model of the revert status dialog
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Stop',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ cancelled = true;
+
+ $('#change-version-status-dialog').modal('setButtonModel', []);
+
+ // we are waiting for the next poll attempt
+ if (revertTimer !== null) {
+ // cancel it
+ clearTimeout(revertTimer);
+
+ // cancel the revert request
+ completeRevertRequest();
+ }
+ }
+ }
+ }]);
+
+ // hide the import dialog immediately
+ $('#import-flow-version-dialog').modal('hide');
+
+ var submitRevertRequest = function () {
+ var revertFlowVersionRequest = {
+ 'processGroupRevision': nfClient.getRevision({
+ 'revision': {
+ 'version': response.processGroupRevision.version
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'versionControlInformation': response.versionControlInformation
+ };
+
+ return $.ajax({
+ type: 'POST',
+ data: JSON.stringify(revertFlowVersionRequest),
+ url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function () {
+ // initialize the progress bar value
+ updateProgress(0);
+
+ // show the progress dialog
+ $('#change-version-status-dialog').modal('show');
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ var pollRevertRequest = function () {
+ getRevertRequest().done(processRevertResponse);
+ };
+
+ var getRevertRequest = function () {
+ return $.ajax({
+ type: 'GET',
+ url: revertRequest.uri,
+ dataType: 'json'
+ }).fail(completeRevertRequest).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ var completeRevertRequest = function () {
+ if (cancelled === true) {
+ // update the message to indicate successful completion
+ $('#change-version-status-message').text('The revert request has been cancelled.');
+
+ // update the button model
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Close',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]);
+ }
+
+ if (nfCommon.isDefinedAndNotNull(revertRequest)) {
+ $.ajax({
+ type: 'DELETE',
+ url: revertRequest.uri + '?' + $.param({
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+ }),
+ dataType: 'json'
+ }).done(function (response) {
+ revertRequest = response.request;
+
+ // update the component that was changing
+ updateProcessGroup(processGroupId);
+
+ if (nfCommon.isDefinedAndNotNull(revertRequest.failureReason)) {
+ // hide the progress dialog
+ $('#change-version-status-dialog').modal('hide');
+
+ nfDialog.showOkDialog({
+ headerText: 'Revert Local Changes',
+ dialogContent: nfCommon.escapeHtml(revertRequest.failureReason)
+ });
+ } else {
+ // update the percent complete
+ updateProgress(revertRequest.percentCompleted);
+
+ // update the message to indicate successful completion
+ $('#change-version-status-message').text('This Process Group version has changed.');
+
+ // update the button model
+ $('#change-version-status-dialog').modal('setButtonModel', [{
+ buttonText: 'Close',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]);
+ }
+ });
+ }
+ };
+
+ var processRevertResponse = function (response) {
+ revertRequest = response.request;
+
+ if (revertRequest.complete === true || cancelled === true) {
+ completeRevertRequest();
+ } else {
+ // update the percent complete
+ updateProgress(revertRequest.percentCompleted);
+
+ // update the status of the revert request
+ $('#change-version-status-message').text(revertRequest.state);
+
+ revertTimer = setTimeout(function () {
+ // clear the timer since we've been invoked
+ revertTimer = null;
+
+ // poll revert request
+ pollRevertRequest();
+ }, 2000);
+ }
+ };
+
+ submitRevertRequest().done(processRevertResponse);
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Revert Changes',
+ dialogContent: 'This Process Group is not currently under version control.'
+ });
+ }
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ return {
+ init: function (timeOffset) {
+ serverTimeOffset = timeOffset;
+
+ // initialize the flow version dialog
+ $('#save-flow-version-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Save Flow Version',
+ buttons: [{
+ buttonText: 'Save',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: function () {
+ if ($('#save-flow-version-registry-combo').is(':visible')) {
+ var selectedRegistry = $('#save-flow-version-registry-combo').combo('getSelectedOption');
+ var selectedBucket = $('#save-flow-version-bucket-combo').combo('getSelectedOption');
+
+ if (nfCommon.isDefinedAndNotNull(selectedRegistry) && nfCommon.isDefinedAndNotNull(selectedBucket)) {
+ return selectedRegistry.disabled === true || selectedBucket.disabled === true;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ },
+ handler: {
+ click: function () {
+ var processGroupId = $('#save-flow-version-process-group-id').text();
+ saveFlowVersion().done(function (response) {
+ updateVersionControlInformation(processGroupId, response.versionControlInformation);
+ });
+
+ $(this).modal('hide');
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {
+ resetSaveFlowVersionDialog();
+ }
+ }
+ });
+
+ // initialize the import flow version dialog
+ $('#import-flow-version-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ handler: {
+ close: function () {
+ resetImportFlowVersionDialog();
+ }
+ }
+ });
+
+ // configure the drop request status dialog
+ $('#change-version-status-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Change Flow Version',
+ handler: {
+ close: function () {
+ // clear the current button model
+ $('#change-version-status-dialog').modal('setButtonModel', []);
+ }
+ }
+ });
+
+ // init the revert local changes dialog
+ $('#revert-local-changes-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Revert Local Changes',
+ buttons: [{
+ buttonText: 'Revert',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ var processGroupId = $('#revert-local-changes-process-group-id').text();
+ revertLocalChanges(processGroupId);
+
+ $(this).modal('hide');
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {
+ resetRevertLocalChangesDialog();
+ }
+ }
+ });
+
+ // init the show local changes dialog
+ $('#show-local-changes-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Show Local Changes',
+ buttons: [{
+ buttonText: 'Close',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {
+ resetShowLocalChangesDialog();
+ }
+ }
+ });
+
+ // handle the click for the process group import
+ $('#import-process-group-link').on('click', function() {
+ showImportFlowVersionDialog();
+ });
+
+ // initialize the import flow version table
+ initImportFlowVersionTable();
+ initLocalChangesTable($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
+ initLocalChangesTable($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
+ },
+
+ /**
+ * Shows the flow version dialog.
+ *
+ * @param processGroupId
+ */
+ showFlowVersionDialog: function (processGroupId) {
+ var focusName = true;
+
+ return $.Deferred(function (deferred) {
+ getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) {
+ if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) {
+ var versionControlInformation = groupVersionControlInformation.versionControlInformation;
+
+ // update the registry and bucket visibility
+ $('#save-flow-version-registry').text(versionControlInformation.registryName).show();
+ $('#save-flow-version-bucket').text(versionControlInformation.bucketName).show();
+ $('#save-flow-version-label').text(versionControlInformation.version + 1);
+
+ $('#save-flow-version-name').text(versionControlInformation.flowName).show();
+ nfCommon.populateField('save-flow-version-description', versionControlInformation.flowDescription);
+ $('#save-flow-version-description').show();
+
+ // record the versionControlInformation
+ $('#save-flow-version-process-group-id').data('versionControlInformation', versionControlInformation);
+
+ // reposition the version label
+ $('#save-flow-version-label').css('margin-top', '-15px');
+
+ focusName = false;
+ deferred.resolve();
+ } else {
+ // update the registry and bucket visibility
+ var registryCombo = $('#save-flow-version-registry-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading registries...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ }).show();
+ var bucketCombo = $('#save-flow-version-bucket-combo').combo('destroy').combo({
+ options: [{
+ text: 'Loading buckets...',
+ value: null,
+ optionClass: 'unset',
+ disabled: true
+ }]
+ }).show();
+
+ // set the initial version
+ $('#save-flow-version-label').text(1);
+
+ $('#save-flow-version-name-field').show();
+ $('#save-flow-version-description-field').show();
+
+ // reposition the version label
+ $('#save-flow-version-label').css('margin-top', '0');
+
+ loadRegistries($('#save-flow-version-dialog'), registryCombo, bucketCombo, null, selectBucketSaveFlowVersion, function (bucketEntity) {
+ return bucketEntity.permissions.canWrite === true;
+ }).done(function () {
+ deferred.resolve();
+ }).fail(function () {
+ deferred.reject();
+ });
+ }
+
+ // record the revision
+ $('#save-flow-version-process-group-id').data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
+ }).fail(nfErrorHandler.handleAjaxError);
+ }).done(function () {
+ $('#save-flow-version-dialog').modal('show');
+
+ if (focusName) {
+ $('#save-flow-version-name-field').focus();
+ } else {
+ $('#save-flow-version-change-comments').focus();
+ }
+ }).fail(function () {
+ $('#save-flow-version-dialog').modal('refreshButtons');
+ }).promise();
+ },
+
+ /**
+ * Reverts local changes for the specified Process Group.
+ *
+ * @param processGroupId
+ */
+ revertLocalChanges: function (processGroupId) {
+ loadLocalChanges(processGroupId, $('#revert-local-changes-message'), $('#revert-local-changes-table'), $('#total-revert-local-changes-entries')).done(function () {
+ $('#revert-local-changes-process-group-id').text(processGroupId);
+ $('#revert-local-changes-dialog').modal('show');
+ });
+ },
+
+ /**
+ * Shows local changes for the specified process group.
+ *
+ * @param processGroupId
+ */
+ showLocalChanges: function (processGroupId) {
+ loadLocalChanges(processGroupId, $('#show-local-changes-message'), $('#show-local-changes-table'), $('#total-show-local-changes-entries')).done(function () {
+ $('#show-local-changes-dialog').modal('show');
+ });
+ },
+
+ /**
+ * Shows the change flow version dialog.
+ *
+ * @param processGroupId
+ */
+ showChangeFlowVersionDialog: function (processGroupId) {
+ return $.Deferred(function (deferred) {
+ getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) {
+ if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) {
+ var versionControlInformation = groupVersionControlInformation.versionControlInformation;
+
+ // update the registry and bucket visibility
+ $('#import-flow-version-registry').text(versionControlInformation.registryName).show();
+ $('#import-flow-version-bucket').text(versionControlInformation.bucketName).show();
+ $('#import-flow-version-name').text(versionControlInformation.flowName).show();
+
+ // show the current version information
+ $('#import-flow-version-container').show();
+ $('#import-flow-version-label').text(versionControlInformation.version);
+
+ // record the versionControlInformation
+ $('#import-flow-version-process-group-id').data('versionControlInformation', versionControlInformation).data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
+
+ // load the flow versions
+ loadFlowVersions(versionControlInformation.registryId, versionControlInformation.bucketId, versionControlInformation.flowId).done(function () {
+ deferred.resolve();
+ }).fail(function () {
+ nfDialog.showOkDialog({
+ headerText: 'Change Version',
+ dialogContent: 'Unable to load available versions for this Process Group.'
+ });
+
+ deferred.reject();
+ });
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Change Version',
+ dialogContent: 'This Process Group is not currently under version control.'
+ });
+
+ deferred.reject();
+ }
+ }).fail(nfErrorHandler.handleAjaxError);
+ }).done(function () {
+ // show the dialog
+ $('#import-flow-version-dialog').modal('setHeaderText', 'Change Version').modal('setButtonModel', [{
+ buttonText: 'Change',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: disableImportOrChangeButton,
+ handler: {
+ click: function () {
+ changeFlowVersion();
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+ }).promise();
+ },
+
+ /**
+ * Stops version control for the specified Process Group.
+ *
+ * @param processGroupId
+ */
+ stopVersionControl: function (processGroupId) {
+ // prompt the user before disconnecting
+ nfDialog.showYesNoDialog({
+ headerText: 'Stop Version Control',
+ dialogContent: 'Are you sure you want to stop version control?',
+ noText: 'Cancel',
+ yesText: 'Disconnect',
+ yesHandler: function () {
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
+ dataType: 'json'
+ }).done(function (response) {
+ if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
+ var revision = nfClient.getRevision({
+ revision: {
+ version: response.processGroupRevision.version
+ }
+ });
+
+ $.ajax({
+ type: 'DELETE',
+ url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId) + '?' + $.param($.extend({
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+ }, revision)),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ updateVersionControlInformation(processGroupId, undefined);
+
+ nfDialog.showOkDialog({
+ headerText: 'Disconnect',
+ dialogContent: 'This Process Group is no longer under version control.'
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ } else {
+ nfDialog.showOkDialog({
+ headerText: 'Disconnect',
+ dialogContent: 'This Process Group is not currently under version control.'
+ })
+ }
+ }).fail(nfErrorHandler.handleAjaxError);
+ }
+ });
+ }
+ };
+}));
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js
new file mode 100644
index 0000000..614472c
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js
@@ -0,0 +1,1744 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global d3, define, module, require, exports */
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'd3',
+ 'nf.Connection',
+ 'nf.Common',
+ 'nf.Client',
+ 'nf.CanvasUtils',
+ 'nf.Dialog'],
+ function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
+ return (nf.ProcessGroup = factory($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.ProcessGroup =
+ factory(require('jquery'),
+ require('d3'),
+ require('nf.Connection'),
+ require('nf.Common'),
+ require('nf.Client'),
+ require('nf.CanvasUtils'),
+ require('nf.Dialog')));
+ } else {
+ nf.ProcessGroup = factory(root.$,
+ root.d3,
+ root.nf.Connection,
+ root.nf.Common,
+ root.nf.Client,
+ root.nf.CanvasUtils,
+ root.nf.Dialog);
+ }
+}
+(this, function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
+ 'use strict';
+
+ var nfConnectable;
+ var nfDraggable;
+ var nfSelectable;
+ var nfContextMenu;
+
+ var PREVIEW_NAME_LENGTH = 30;
+
+ var dimensions = {
+ width: 380,
+ height: 172
+ };
+
+ // ----------------------------
+ // process groups currently on the graph
+ // ----------------------------
+
+ var processGroupMap;
+
+ // -----------------------------------------------------------
+ // cache for components that are added/removed from the canvas
+ // -----------------------------------------------------------
+
+ var removedCache;
+ var addedCache;
+
+ // --------------------
+ // component containers
+ // --------------------
+
+ var processGroupContainer;
+
+ // --------------------------
+ // privately scoped functions
+ // --------------------------
+
+ /**
+ * Determines whether the specified process group is under version control.
+ *
+ * @param d
+ */
+ var isUnderVersionControl = function (d) {
+ return nfCommon.isDefinedAndNotNull(d.versionedFlowState);
+ };
+
+ /**
+ * Selects the process group elements against the current process group map.
+ */
+ var select = function () {
+ return processGroupContainer.selectAll('g.process-group').data(processGroupMap.values(), function (d) {
+ return d.id;
+ });
+ };
+
+
+ /**
+ * Renders the process groups in the specified selection.
+ *
+ * @param {selection} entered The selection of process groups to be rendered
+ * @param {boolean} selected Whether the process group should be selected
+ * @return the entered selection
+ */
+ var renderProcessGroups = function (entered, selected) {
+ if (entered.empty()) {
+ return entered;
+ }
+
+ var processGroup = entered.append('g')
+ .attrs({
+ 'id': function (d) {
+ return 'id-' + d.id;
+ },
+ 'class': 'process-group component'
+ })
+ .classed('selected', selected)
+ .call(nfCanvasUtils.position);
+
+ // ----
+ // body
+ // ----
+
+ // process group border
+ processGroup.append('rect')
+ .attrs({
+ 'class': 'border',
+ 'width': function (d) {
+ return d.dimensions.width;
+ },
+ 'height': function (d) {
+ return d.dimensions.height;
+ },
+ 'fill': 'transparent',
+ 'stroke': 'transparent'
+ });
+
+ // process group body
+ processGroup.append('rect')
+ .attrs({
+ 'class': 'body',
+ 'width': function (d) {
+ return d.dimensions.width;
+ },
+ 'height': function (d) {
+ return d.dimensions.height;
+ },
+ 'filter': 'url(#component-drop-shadow)',
+ 'stroke-width': 0
+ });
+
+ // process group name background
+ processGroup.append('rect')
+ .attrs({
+ 'width': function (d) {
+ return d.dimensions.width;
+ },
+ 'height': 32,
+ 'fill': '#b8c6cd'
+ });
+
+ // process group name
+ processGroup.append('text')
+ .attrs({
+ 'x': 10,
+ 'y': 20,
+ 'width': 300,
+ 'height': 16,
+ 'class': 'process-group-name'
+ });
+
+ // process group name
+ processGroup.append('text')
+ .attrs({
+ 'x': 10,
+ 'y': 21,
+ 'class': 'version-control'
+ });
+
+ console.log(processGroup);
+
+
+ // always support selecting and navigation
+ processGroup.on('dblclick', function (d) {
+ // enter this group on double click
+ nfProcessGroup.enterGroup(d.id);
+ })
+ .call(nfSelectable.activate).call(nfContextMenu.activate);
+
+ // only support dragging, connection, and drag and drop if appropriate
+ processGroup.filter(function (d) {
+ return d.permissions.canWrite && d.permissions.canRead;
+ })
+ .on('mouseover.drop', function (d) {
+ // Using mouseover/out to workaround chrome issue #122746
+
+ // get the target and ensure its not already been marked for drop
+ var target = d3.select(this);
+ if (!target.classed('drop')) {
+ var targetData = target.datum();
+
+ // see if there is a selection being dragged
+ var drag = d3.select('rect.drag-selection');
+ if (!drag.empty()) {
+ // filter the current selection by this group
+ var selection = nfCanvasUtils.getSelection().filter(function (d) {
+ return targetData.id === d.id;
+ });
+
+ // ensure this group isn't in the selection
+ if (selection.empty()) {
+ // mark that we are hovering over a drop area if appropriate
+ target.classed('drop', function () {
+ // get the current selection and ensure its disconnected
+ return nfConnection.isDisconnected(nfCanvasUtils.getSelection());
+ });
+ }
+ }
+ }
+ })
+ .on('mouseout.drop', function (d) {
+ // mark that we are no longer hovering over a drop area unconditionally
+ d3.select(this).classed('drop', false);
+ })
+ .call(nfDraggable.activate)
+ .call(nfConnectable.activate);
+
+ return processGroup;
+ };
+
+ // attempt of space between component count and icon for process group contents
+ var CONTENTS_SPACER = 10;
+ var CONTENTS_VALUE_SPACER = 5;
+
+ /**
+ * Updates the process groups in the specified selection.
+ *
+ * @param {selection} updated The process groups to be updated
+ */
+ var updateProcessGroups = function (updated) {
+ if (updated.empty()) {
+ return;
+ }
+
+ // process group border authorization
+ updated.select('rect.border')
+ .classed('unauthorized', function (d) {
+ return d.permissions.canRead === false;
+ });
+
+ // process group body authorization
+ updated.select('rect.body')
+ .classed('unauthorized', function (d) {
+ return d.permissions.canRead === false;
+ });
+
+ updated.each(function (processGroupData) {
+ var processGroup = d3.select(this);
+ var details = processGroup.select('g.process-group-details');
+
+ // update the component behavior as appropriate
+ nfCanvasUtils.editable(processGroup, nfConnectable, nfDraggable);
+
+ // if this processor is visible, render everything
+ if (processGroup.classed('visible')) {
+ if (details.empty()) {
+ details = processGroup.append('g').attr('class', 'process-group-details');
+
+ // -------------------
+ // contents background
+ // -------------------
+
+ details.append('rect')
+ .attrs({
+ 'x': 0,
+ 'y': 32,
+ 'width': function () {
+ return processGroupData.dimensions.width
+ },
+ 'height': 24,
+ 'fill': '#e3e8eb'
+ });
+
+ details.append('rect')
+ .attrs({
+ 'x': 0,
+ 'y': function () {
+ return processGroupData.dimensions.height - 24;
+ },
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 24,
+ 'fill': '#e3e8eb'
+ });
+
+ // --------
+ // contents
+ // --------
+
+ // transmitting icon
+// details.append('text')
+// .attrs({
+// 'x': 10,
+// 'y': 49,
+// 'class': 'process-group-transmitting process-group-contents-icon',
+// 'font-family': 'FontAwesome'
+// })
+// .text('\uf140')
+// .append("title")
+// .text("Transmitting Remote Process Groups");
+
+
+ // transmitting count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-transmitting-count process-group-contents-count'
+// });
+
+ // not transmitting icon
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-not-transmitting process-group-contents-icon',
+// 'font-family': 'flowfont'
+// })
+// .text('\ue80a')
+// .append("title")
+// .text("Not Transmitting Remote Process Groups");
+
+ // not transmitting count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-not-transmitting-count process-group-contents-count'
+// });
+
+ // running icon
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-running process-group-contents-icon',
+// 'font-family': 'FontAwesome'
+// })
+// .text('\uf04b')
+// .append("title")
+// .text("Running Components");
+
+ // running count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-running-count process-group-contents-count'
+// });
+
+ // stopped icon
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-stopped process-group-contents-icon',
+// 'font-family': 'FontAwesome'
+// })
+// .text('\uf04d')
+// .append("title")
+// .text("Stopped Components");
+
+ // stopped count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-stopped-count process-group-contents-count'
+// });
+
+ // invalid icon
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-invalid process-group-contents-icon',
+// 'font-family': 'FontAwesome'
+// })
+// .text('\uf071')
+// .append("title")
+// .text("Invalid Components");
+
+ // invalid count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-invalid-count process-group-contents-count'
+// });
+
+ // disabled icon
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-disabled process-group-contents-icon',
+// 'font-family': 'flowfont'
+// })
+// .text('\ue802')
+// .append("title")
+// .text("Disabled Components");
+
+ // disabled count
+// details.append('text')
+// .attrs({
+// 'y': 49,
+// 'class': 'process-group-disabled-count process-group-contents-count'
+// });
+
+ // up to date icon
+ details.append('text')
+ .attrs({
+ 'x': 10,
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-up-to-date process-group-contents-icon',
+ 'font-family': 'FontAwesome'
+ })
+ .text('\uf00c')
+ .append("title")
+ .text("Up to date Versioned Process Groups");
+
+ // up to date count
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-up-to-date-count process-group-contents-count'
+ });
+
+ // locally modified icon
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-locally-modified process-group-contents-icon',
+ 'font-family': 'FontAwesome'
+ })
+ .text('\uf069')
+ .append("title")
+ .text("Locally modified Versioned Process Groups");
+
+ // locally modified count
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-locally-modified-count process-group-contents-count'
+ });
+
+ // stale icon
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-stale process-group-contents-icon',
+ 'font-family': 'FontAwesome'
+ })
+ .text('\uf0aa')
+ .append("title")
+ .text("Stale Versioned Process Groups");
+
+ // stale count
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-stale-count process-group-contents-count'
+ });
+
+ // locally modified and stale icon
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-locally-modified-and-stale process-group-contents-icon',
+ 'font-family': 'FontAwesome'
+ })
+ .text('\uf06a')
+ .append("title")
+ .text("Locally modified and stale Versioned Process Groups");
+
+ // locally modified and stale count
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-locally-modified-and-stale-count process-group-contents-count'
+ });
+
+ // sync failure icon
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-sync-failure process-group-contents-icon',
+ 'font-family': 'FontAwesome'
+ })
+ .text('\uf128')
+ .append("title")
+ .text("Sync failure Versioned Process Groups");
+
+ // sync failure count
+ details.append('text')
+ .attrs({
+ 'y': function () {
+ return processGroupData.dimensions.height - 7;
+ },
+ 'class': 'process-group-sync-failure-count process-group-contents-count'
+ });
+
+ // ----------------
+ // stats background
+ // ----------------
+
+ // queued
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 19,
+ 'x': 0,
+ 'y': 66,
+ 'fill': '#f4f6f7'
+ });
+
+ // border
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 1,
+ 'x': 0,
+ 'y': 84,
+ 'fill': '#c7d2d7'
+ });
+
+ // in
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 19,
+ 'x': 0,
+ 'y': 85,
+ 'fill': '#ffffff'
+ });
+
+ // border
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 1,
+ 'x': 0,
+ 'y': 103,
+ 'fill': '#c7d2d7'
+ });
+
+ // read/write
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 19,
+ 'x': 0,
+ 'y': 104,
+ 'fill': '#f4f6f7'
+ });
+
+ // border
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 1,
+ 'x': 0,
+ 'y': 122,
+ 'fill': '#c7d2d7'
+ });
+
+ // out
+ details.append('rect')
+ .attrs({
+ 'width': function () {
+ return processGroupData.dimensions.width;
+ },
+ 'height': 19,
+ 'x': 0,
+ 'y': 123,
+ 'fill': '#ffffff'
+ });
+
+ // -----
+ // stats
+ // -----
+
+ // stats label container
+ var processGroupStatsLabel = details.append('g')
+ .attrs({
+ 'transform': 'translate(6, 75)'
+ });
+
+ // queued label
+ processGroupStatsLabel.append('text')
+ .attrs({
+ 'width': 73,
+ 'height': 10,
+ 'x': 4,
+ 'y': 5,
+ 'class': 'stats-label'
+ })
+ .text('Queued');
+
+ // in label
+ processGroupStatsLabel.append('text')
+ .attrs({
+ 'width': 73,
+ 'height': 10,
+ 'x': 4,
+ 'y': 24,
+ 'class': 'stats-label'
+ })
+ .text('In');
+
+ // read/write label
+ processGroupStatsLabel.append('text')
+ .attrs({
+ 'width': 73,
+ 'height': 10,
+ 'x': 4,
+ 'y': 42,
+ 'class': 'stats-label'
+ })
+ .text('Read/Write');
+
+ // out label
+ processGroupStatsLabel.append('text')
+ .attrs({
+ 'width': 73,
+ 'height': 10,
+ 'x': 4,
+ 'y': 60,
+ 'class': 'stats-label'
+ })
+ .text('Out');
+
+ // stats value container
+ var processGroupStatsValue = details.append('g')
+ .attrs({
+ 'transform': 'translate(95, 75)'
+ });
+
+ // queued value
+ var queuedText = processGroupStatsValue.append('text')
+ .attrs({
+ 'width': 180,
+ 'height': 10,
+ 'x': 4,
+ 'y': 5,
+ 'class': 'process-group-queued stats-value'
+ });
+
+ // queued count
+ queuedText.append('tspan')
+ .attrs({
+ 'class': 'count'
+ });
+
+ // queued size
+ queuedText.append('tspan')
+ .attrs({
+ 'class': 'size'
+ });
+
+ // in value
+ var inText = processGroupStatsValue.append('text')
+ .attrs({
+ 'width': 180,
+ 'height': 10,
+ 'x': 4,
+ 'y': 24,
+ 'class': 'process-group-in stats-value'
+ });
+
+ // in count
+ inText.append('tspan')
+ .attrs({
+ 'class': 'count'
+ });
+
+ // in size
+ inText.append('tspan')
+ .attrs({
+ 'class': 'size'
+ });
+
+ // in
+ inText.append('tspan')
+ .attrs({
+ 'class': 'ports'
+ });
+
+ // read/write value
+ processGroupStatsValue.append('text')
+ .attrs({
+ 'width': 180,
+ 'height': 10,
+ 'x': 4,
+ 'y': 42,
+ 'class': 'process-group-read-write stats-value'
+ });
+
+ // out value
+ var outText = processGroupStatsValue.append('text')
+ .attrs({
+ 'width': 180,
+ 'height': 10,
+ 'x': 4,
+ 'y': 60,
+ 'class': 'process-group-out stats-value'
+ });
+
+ // out ports
+ outText.append('tspan')
+ .attrs({
+ 'class': 'ports'
+ });
+
+ // out count
+ outText.append('tspan')
+ .attrs({
+ 'class': 'count'
+ });
+
+ // out size
+ outText.append('tspan')
+ .attrs({
+ 'class': 'size'
+ });
+
+ // stats value container
+ var processGroupStatsInfo = details.append('g')
+ .attrs({
+ 'transform': 'translate(335, 75)'
+ });
+
+ // in info
+ processGroupStatsInfo.append('text')
+ .attrs({
+ 'width': 25,
+ 'height': 10,
+ 'x': 4,
+ 'y': 24,
+ 'class': 'stats-info'
+ })
+ .text('5 min');
+
+ // read/write info
+ processGroupStatsInfo.append('text')
+ .attrs({
+ 'width': 25,
+ 'height': 10,
+ 'x': 4,
+ 'y': 42,
+ 'class': 'stats-info'
+ })
+ .text('5 min');
+
+ // out info
+ processGroupStatsInfo.append('text')
+ .attrs({
+ 'width': 25,
+ 'height': 10,
+ 'x': 4,
+ 'y': 60,
+ 'class': 'stats-info'
+ })
+ .text('5 min');
+
+ // --------
+ // comments
+ // --------
+
+ details.append('path')
+ .attrs({
+ 'class': 'component-comments',
+ 'transform': 'translate(' + (processGroupData.dimensions.width - 2) + ', ' + (processGroupData.dimensions.height - 10) + ')',
+ 'd': 'm0,0 l0,8 l-8,0 z'
+ });
+
+ // -------------------
+ // active thread count
+ // -------------------
+
+ // active thread count
+ details.append('text')
+ .attrs({
+ 'class': 'active-thread-count-icon',
+ 'y': 20
+ })
+ .text('\ue83f');
+
+ // active thread icon
+ details.append('text')
+ .attrs({
+ 'class': 'active-thread-count',
+ 'y': 20
+ });
+
+ // ---------
+ // bulletins
+ // ---------
+
+ // bulletin background
+ details.append('rect')
+ .attrs({
+ 'class': 'bulletin-background',
+ 'x': function () {
+ return processGroupData.dimensions.width - 24;
+ },
+ 'y': 32,
+ 'width': 24,
+ 'height': 24
+ });
+
+ // bulletin icon
+ details.append('text')
+ .attrs({
+ 'class': 'bulletin-icon',
+ 'x': function () {
+ return processGroupData.dimensions.width - 17;
+ },
+ 'y': 49
+ })
+ .text('\uf24a');
+ }
+
+ // update transmitting
+ var transmitting = details.select('text.process-group-transmitting')
+ .classed('transmitting', function (d) {
+ return d.permissions.canRead && d.activeRemotePortCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.activeRemotePortCount === 0;
+ });
+ var transmittingCount = details.select('text.process-group-transmitting-count')
+ .attr('x', function () {
+ var transmittingCountX = parseInt(transmitting.attr('x'), 10);
+ return transmittingCountX + Math.round(transmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.activeRemotePortCount;
+ });
+ transmittingCount.append("title").text("Transmitting Remote Process Groups");
+
+ // update not transmitting
+ var notTransmitting = details.select('text.process-group-not-transmitting')
+ .classed('not-transmitting', function (d) {
+ return d.permissions.canRead && d.inactiveRemotePortCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.inactiveRemotePortCount === 0;
+ })
+ .attr('x', function () {
+ var transmittingX = parseInt(transmittingCount.attr('x'), 10);
+ return transmittingX + Math.round(transmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var notTransmittingCount = details.select('text.process-group-not-transmitting-count')
+ .attr('x', function () {
+ var notTransmittingCountX = parseInt(notTransmitting.attr('x'), 10);
+ return notTransmittingCountX + Math.round(notTransmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.inactiveRemotePortCount;
+ });
+ notTransmittingCount.append("title").text("Not transmitting Remote Process Groups")
+
+ // update running
+ var running = details.select('text.process-group-running')
+ .classed('running', function (d) {
+ return d.permissions.canRead && d.component.runningCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.runningCount === 0;
+ })
+ .attr('x', function () {
+ var notTransmittingX = parseInt(notTransmittingCount.attr('x'), 10);
+ return notTransmittingX + Math.round(notTransmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var runningCount = details.select('text.process-group-running-count')
+ .attr('x', function () {
+ var runningCountX = parseInt(running.attr('x'), 10);
+ return runningCountX + Math.round(running.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.runningCount;
+ });
+ runningCount.append("title").text("Running Components");
+
+ // update stopped
+ var stopped = details.select('text.process-group-stopped')
+ .classed('stopped', function (d) {
+ return d.permissions.canRead && d.component.stoppedCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.stoppedCount === 0;
+ })
+ .attr('x', function () {
+ var runningX = parseInt(runningCount.attr('x'), 10);
+ return runningX + Math.round(runningCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var stoppedCount = details.select('text.process-group-stopped-count')
+ .attr('x', function () {
+ var stoppedCountX = parseInt(stopped.attr('x'), 10);
+ return stoppedCountX + Math.round(stopped.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.stoppedCount;
+ });
+ stoppedCount.append("title").text("Stopped Components");
+
+ // update invalid
+ var invalid = details.select('text.process-group-invalid')
+ .classed('invalid', function (d) {
+ return d.permissions.canRead && d.component.invalidCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.invalidCount === 0;
+ })
+ .attr('x', function () {
+ var stoppedX = parseInt(stoppedCount.attr('x'), 10);
+ return stoppedX + Math.round(stoppedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var invalidCount = details.select('text.process-group-invalid-count')
+ .attr('x', function () {
+ var invalidCountX = parseInt(invalid.attr('x'), 10);
+ return invalidCountX + Math.round(invalid.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.invalidCount;
+ });
+ invalidCount.append("title").text("Invalid Components");
+
+ // update disabled
+ var disabled = details.select('text.process-group-disabled')
+ .classed('disabled', function (d) {
+ return d.permissions.canRead && d.component.disabledCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.disabledCount === 0;
+ })
+ .attr('x', function () {
+ var invalidX = parseInt(invalidCount.attr('x'), 10);
+ return invalidX + Math.round(invalidCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var disabledCount = details.select('text.process-group-disabled-count')
+ .attr('x', function () {
+ var disabledCountX = parseInt(disabled.attr('x'), 10);
+ return disabledCountX + Math.round(disabled.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.disabledCount;
+ });
+ disabledCount.append("title").text("Disabled Components");
+
+ // up to date current
+ var upToDate = details.select('text.process-group-up-to-date')
+ .classed('up-to-date', function (d) {
+ return d.permissions.canRead && d.component.upToDateCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.upToDateCount === 0;
+ });
+ var upToDateCount = details.select('text.process-group-up-to-date-count')
+ .attr('x', function () {
+ var updateToDateCountX = parseInt(upToDate.attr('x'), 10);
+ return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.upToDateCount;
+ });
+ upToDateCount.append("title").text("Up to date Versioned Process Groups");
+
+ // update locally modified
+ var locallyModified = details.select('text.process-group-locally-modified')
+ .classed('locally-modified', function (d) {
+ return d.permissions.canRead && d.component.locallyModifiedCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.locallyModifiedCount === 0;
+ })
+ .attr('x', function () {
+ var upToDateX = parseInt(upToDateCount.attr('x'), 10);
+ return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var locallyModifiedCount = details.select('text.process-group-locally-modified-count')
+ .attr('x', function () {
+ var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10);
+ return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.locallyModifiedCount;
+ });
+ locallyModifiedCount.append("title").text("Locally modified Versioned Process Groups");
+
+ // update stale
+ var stale = details.select('text.process-group-stale')
+ .classed('stale', function (d) {
+ return d.permissions.canRead && d.component.staleCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.staleCount === 0;
+ })
+ .attr('x', function () {
+ var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10);
+ return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var staleCount = details.select('text.process-group-stale-count')
+ .attr('x', function () {
+ var staleCountX = parseInt(stale.attr('x'), 10);
+ return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.staleCount;
+ });
+ staleCount.append("title").text("Stale Versioned Process Groups");
+
+ // update locally modified and stale
+ var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale')
+ .classed('locally-modified-and-stale', function (d) {
+ return d.permissions.canRead && d.component.locallyModifiedAndStaleCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.locallyModifiedAndStaleCount === 0;
+ })
+ .attr('x', function () {
+ var staleX = parseInt(staleCount.attr('x'), 10);
+ return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+ });
+ var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count')
+ .attr('x', function () {
+ var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10);
+ return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.locallyModifiedAndStaleCount;
+ });
+ locallyModifiedAndStaleCount.append("title").text("Locally modified and stale Versioned Process Groups");
+
+ // update sync failure
+ var syncFailure = details.select('text.process-group-sync-failure')
+ .classed('sync-failure', function (d) {
+ return d.permissions.canRead && d.component.syncFailureCount > 0;
+ })
+ .classed('zero', function (d) {
+ return d.permissions.canRead && d.component.syncFailureCount === 0;
+ })
+ .attr('x', function () {
+ var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10);
+ return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2;
+ });
+ var syncFailureCount = details.select('text.process-group-sync-failure-count')
+ .attr('x', function () {
+ var syncFailureCountX = parseInt(syncFailure.attr('x'), 10);
+ return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ })
+ .text(function (d) {
+ return d.syncFailureCount;
+ });
+ syncFailureCount.append("title").text("Sync failure Versioned Process Groups");
+
+ /**
+ * update version control information
+ * @author: Renu
+ * @desc for lines 1110-1201: based on state of the process group, environment selection and submit button enable/disable
+ */
+ var versionControl = processGroup.select('text.version-control')
+ .styles({
+ 'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden',
+ 'fill': function () {
+ if (isUnderVersionControl(processGroupData)) {
+ var vciState = processGroupData.versionedFlowState;
+ if (vciState === 'SYNC_FAILURE') {
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return '#666666';
+ } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
+ console.log("locally but stale in style");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return '#BA554A';
+ } else if (vciState === 'STALE') {
+ console.log("stale in style");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ return '#BA554A';
+ } else if (vciState === 'LOCALLY_MODIFIED') {
+ console.log("locally modified in style");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return '#666666';
+ } else {
+ return '#1A9964';
+ $('#environmentType').prop('disabled', false);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ }
+ } else {
+ $('#environmentType').prop('disabled', true);
+ return '#000';
+ }
+ }
+ })
+ .text(function () {
+ if (isUnderVersionControl(processGroupData)) {
+ var vciState = processGroupData.versionedFlowState;
+ if (vciState === 'SYNC_FAILURE') {
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return '\uf128'
+ } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
+ console.log("locally but stale in text");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+
+ return '\uf06a';
+ } else if (vciState === 'STALE') {
+ console.log("stale in text");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ return '\uf0aa';
+ } else if (vciState === 'LOCALLY_MODIFIED') {
+ console.log("locally modified in text");
+ $('#environmentType').prop('disabled', true);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ return '\uf069';
+ } else {
+ return '\uf00c';
+ $('#environmentType').prop('disabled', false);
+ if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
+ $('#operate-submit-btn').prop('disabled', false);
+ }else{$('#operate-submit-btn').prop('disabled', true);}
+ }
+ } else {
+ $('#environmentType').prop('disabled', true);
+ return '';
+ }
+ });
+
+ if (processGroupData.permissions.canRead) {
+ // version control tooltip
+ versionControl.each(function () {
+ // get the tip
+ var tip = d3.select('#version-control-tip-' + processGroupData.id);
+
+ // if there are validation errors generate a tooltip
+ if (isUnderVersionControl(processGroupData)) {
+ // create the tip if necessary
+ if (tip.empty()) {
+ tip = d3.select('#process-group-tooltips').append('div')
+ .attr('id', function () {
+ return 'version-control-tip-' + processGroupData.id;
+ })
+ .attr('class', 'tooltip nifi-tooltip');
+ }
+
+ // update the tip
+ tip.html(function () {
+ var vci = processGroupData.component.versionControlInformation;
+ var versionControlTip = $('<div></div>').text('Tracking to "' + vci.flowName + '" version ' + vci.version + ' in "' + vci.registryName + ' - ' + vci.bucketName + '"');
+ var versionControlStateTip = $('<div></div>').text(nfCommon.getVersionControlTooltip(vci));
+ return $('<div></div>').append(versionControlTip).append('<br/>').append(versionControlStateTip).html();
+ });
+
+ // add the tooltip
+ nfCanvasUtils.canvasTooltip(tip, d3.select(this));
+ } else {
+ // remove the tip if necessary
+ if (!tip.empty()) {
+ tip.remove();
+ }
+ }
+ });
+
+ // update the process group comments
+ processGroup.select('path.component-comments')
+ .style('visibility', nfCommon.isBlank(processGroupData.component.comments) ? 'hidden' : 'visible')
+ .each(function () {
+ // get the tip
+ var tip = d3.select('#comments-tip-' + processGroupData.id);
+
+ // if there are validation errors generate a tooltip
+ if (nfCommon.isBlank(processGroupData.component.comments)) {
+ // remove the tip if necessary
+ if (!tip.empty()) {
+ tip.remove();
+ }
+ } else {
+ // create the tip if necessary
+ if (tip.empty()) {
+ tip = d3.select('#process-group-tooltips').append('div')
+ .attr('id', function () {
+ return 'comments-tip-' + processGroupData.id;
+ })
+ .attr('class', 'tooltip nifi-tooltip');
+ }
+
+ // update the tip
+ tip.text(processGroupData.component.comments);
+
+ // add the tooltip
+ nfCanvasUtils.canvasTooltip(tip, d3.select(this));
+ }
+ });
+
+ // update the process group name
+ processGroup.select('text.process-group-name')
+ .attrs({
+ 'x': function () {
+ if (isUnderVersionControl(processGroupData)) {
+ var versionControlX = parseInt(versionControl.attr('x'), 10);
+ return versionControlX + Math.round(versionControl.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+ } else {
+ return 10;
+ }
+ },
+ 'width': function () {
+ if (isUnderVersionControl(processGroupData)) {
+ var versionControlX = parseInt(versionControl.attr('x'), 10);
+ var processGroupNameX = parseInt(d3.select(this).attr('x'), 10);
+ return 300 - (processGroupNameX - versionControlX);
+ } else {
+ return 300;
+ }
+ }
+ })
+ .each(function (d) {
+ var processGroupName = d3.select(this);
+
+ // reset the process group name to handle any previous state
+ processGroupName.text(null).selectAll('title').remove();
+
+ // apply ellipsis to the process group name as necessary
+ nfCanvasUtils.ellipsis(processGroupName, d.component.name);
+ })
+ .append('title')
+ .text(function (d) {
+ return d.component.name;
+ });
+ } else {
+ // clear the process group comments
+ processGroup.select('path.component-comments').style('visibility', 'hidden');
+
+ // clear the process group name
+ processGroup.select('text.process-group-name')
+ .attrs({
+ 'x': 10,
+ 'width': 316
+ })
+ .text(null);
+
+ // clear tooltips
+ processGroup.call(removeTooltips);
+ }
+
+ // populate the stats
+ processGroup.call(updateProcessGroupStatus);
+ } else {
+ if (processGroupData.permissions.canRead) {
+ // update the process group name
+ processGroup.select('text.process-group-name')
+ .text(function (d) {
+ var name = d.component.name;
+ if (name.length > PREVIEW_NAME_LENGTH) {
+ return name.substring(0, PREVIEW_NAME_LENGTH) + String.fromCharCode(8230);
+ } else {
+ return name;
+ }
+ });
+ } else {
+ // clear the process group name
+ processGroup.select('text.process-group-name').text(null);
+ }
+
+ // remove the tooltips
+ processGroup.call(removeTooltips);
+
+ // remove the details if necessary
+ if (!details.empty()) {
+ details.remove();
+ }
+ }
+ });
+ };
+
+ /**
+ * Updates the process group status.
+ *
+ * @param {selection} updated The process groups to be updated
+ */
+ var updateProcessGroupStatus = function (updated) {
+ if (updated.empty()) {
+ return;
+ }
+
+ // queued count value
+ updated.select('text.process-group-queued tspan.count')
+ .text(function (d) {
+ return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.queued, ' ');
+ });
+
+ // queued size value
+ updated.select('text.process-group-queued tspan.size')
+ .text(function (d) {
+ return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.queued, ' ');
+ });
+
+ // in count value
+ updated.select('text.process-group-in tspan.count')
+ .text(function (d) {
+ return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.input, ' ');
+ });
+
+ // in size value
+ updated.select('text.process-group-in tspan.size')
+ .text(function (d) {
+ return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.input, ' ');
+ });
+
+ // in ports value
+ updated.select('text.process-group-in tspan.ports')
+ .text(function (d) {
+ return ' ' + String.fromCharCode(8594) + ' ' + d.inputPortCount;
+ });
+
+ // read/write value
+ updated.select('text.process-group-read-write')
+ .text(function (d) {
+ return d.status.aggregateSnapshot.read + ' / ' + d.status.aggregateSnapshot.written;
+ });
+
+ // out ports value
+ updated.select('text.process-group-out tspan.ports')
+ .text(function (d) {
+ return d.outputPortCount + ' ' + String.fromCharCode(8594) + ' ';
+ });
+
+ // out count value
+ updated.select('text.process-group-out tspan.count')
+ .text(function (d) {
+ return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.output, ' ');
+ });
+
+ // out size value
+ updated.select('text.process-group-out tspan.size')
+ .text(function (d) {
+ return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.output, ' ');
+ });
+
+ updated.each(function (d) {
+ var processGroup = d3.select(this);
+ var offset = 0;
+
+ // -------------------
+ // active thread count
+ // -------------------
+
+ nfCanvasUtils.activeThreadCount(processGroup, d, function (off) {
+ offset = off;
+ });
+
+ // ---------
+ // bulletins
+ // ---------
+
+ processGroup.select('rect.bulletin-background').classed('has-bulletins', function () {
+ return !nfCommon.isEmpty(d.status.aggregateSnapshot.bulletins);
+ });
+
+ nfCanvasUtils.bulletins(processGroup, d, function () {
+ return d3.select('#process-group-tooltips');
+ }, offset);
+ });
+ };
+
+ /**
+ * Removes the process groups in the specified selection.
+ *
+ * @param {selection} removed The process groups to be removed
+ */
+ var removeProcessGroups = function (removed) {
+ if (removed.empty()) {
+ return;
+ }
+
+ removed.call(removeTooltips).remove();
+ };
+
+ /**
+ * Removes the tooltips for the process groups in the specified selection.
+ *
+ * @param {selection} removed
+ */
+ var removeTooltips = function (removed) {
+ removed.each(function (d) {
+ // remove any associated tooltips
+ $('#bulletin-tip-' + d.id).remove();
+ $('#version-control-tip-' + d.id).remove();
+ $('#comments-tip-' + d.id).remove();
+ });
+ };
+
+ var nfProcessGroup = {
+ /**
+ * Initializes of the Process Group handler.
+ *
+ * @param nfConnectableRef The nfConnectable module.
+ * @param nfDraggableRef The nfDraggable module.
+ * @param nfSelectableRef The nfSelectable module.
+ * @param nfContextMenuRef The nfContextMenu module.
+ */
+ init: function (nfConnectableRef, nfDraggableRef, nfSelectableRef, nfContextMenuRef) {
+ nfConnectable = nfConnectableRef;
+ nfDraggable = nfDraggableRef;
+ nfSelectable = nfSelectableRef;
+ nfContextMenu = nfContextMenuRef;
+
+ processGroupMap = d3.map();
+ removedCache = d3.map();
+ addedCache = d3.map();
+
+ // create the process group container
+ processGroupContainer = d3.select('#canvas').append('g')
+ .attrs({
+ 'pointer-events': 'all',
+ 'class': 'process-groups'
+ });
+ },
+
+ /**
+ * Adds the specified process group entity.
+ *
+ * @param processGroupEntities The process group
+ * @param options Configuration options
+ */
+ add: function (processGroupEntities, options) {
+ var selectAll = false;
+ if (nfCommon.isDefinedAndNotNull(options)) {
+ selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
+ }
+
+ // get the current time
+ var now = new Date().getTime();
+
+ var add = function (processGroupEntity) {
+ addedCache.set(processGroupEntity.id, now);
+
+ // add the process group
+ processGroupMap.set(processGroupEntity.id, $.extend({
+ type: 'ProcessGroup',
+ dimensions: dimensions
+ }, processGroupEntity));
+ };
+
+ // determine how to handle the specified process groups
+ if ($.isArray(processGroupEntities)) {
+ $.each(processGroupEntities, function (_, processGroupEntity) {
+ add(processGroupEntity);
+ });
+ } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
+ add(processGroupEntities);
+ }
+
+ // select
+ var selection = select();
+
+ // enter
+ var entered = renderProcessGroups(selection.enter(), selectAll);
+
+ // update
+ updateProcessGroups(selection.merge(entered));
+ },
+
+ /**
+ * Populates the graph with the specified process groups.
+ *
+ * @argument {object | array} processGroupEntities The process groups to add
+ * @argument {object} options Configuration options
+ */
+ set: function (processGroupEntities, options) {
+ var selectAll = false;
+ var transition = false;
+ var overrideRevisionCheck = false;
+ if (nfCommon.isDefinedAndNotNull(options)) {
+ selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
+ transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition;
+ overrideRevisionCheck = nfCommon.isDefinedAndNotNull(options.overrideRevisionCheck) ? options.overrideRevisionCheck : overrideRevisionCheck;
+ }
+
+ var set = function (proposedProcessGroupEntity) {
+ var currentProcessGroupEntity = processGroupMap.get(proposedProcessGroupEntity.id);
+
+ // set the process group if appropriate due to revision and wasn't previously removed
+ if ((nfClient.isNewerRevision(currentProcessGroupEntity, proposedProcessGroupEntity) && !removedCache.has(proposedProcessGroupEntity.id)) || overrideRevisionCheck === true) {
+ processGroupMap.set(proposedProcessGroupEntity.id, $.extend({
+ type: 'ProcessGroup',
+ dimensions: dimensions
+ }, proposedProcessGroupEntity));
+ }
+ };
+
+ // determine how to handle the specified process groups
+ if ($.isArray(processGroupEntities)) {
+ $.each(processGroupMap.keys(), function (_, key) {
+ var currentProcessGroupEntity = processGroupMap.get(key);
+ var isPresent = $.grep(processGroupEntities, function (proposedProcessGroupEntity) {
+ return proposedProcessGroupEntity.id === currentProcessGroupEntity.id;
+ });
+
+ // if the current process group is not present and was not recently added, remove it
+ if (isPresent.length === 0 && !addedCache.has(key)) {
+ processGroupMap.remove(key);
+ }
+ });
+ $.each(processGroupEntities, function (_, processGroupEntity) {
+ set(processGroupEntity);
+ });
+ } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
+ set(processGroupEntities);
+ }
+
+ // select
+ var selection = select();
+
+ // enter
+ var entered = renderProcessGroups(selection.enter(), selectAll);
+
+ // update
+ var updated = selection.merge(entered);
+ updated.call(updateProcessGroups).call(nfCanvasUtils.position, transition);
+
+ // exit
+ selection.exit().call(removeProcessGroups);
+ },
+
+ /**
+ * If the process group id is specified it is returned. If no process group id
+ * specified, all process groups are returned.
+ *
+ * @param {string} id
+ */
+ get: function (id) {
+ if (nfCommon.isUndefined(id)) {
+ return processGroupMap.values();
+ } else {
+ return processGroupMap.get(id);
+ }
+ },
+
+ /**
+ * If the process group id is specified it is refresh according to the current
+ * state. If no process group id is specified, all process groups are refreshed.
+ *
+ * @param {string} id Optional
+ */
+ refresh: function (id) {
+ if (nfCommon.isDefinedAndNotNull(id)) {
+ d3.select('#id-' + id).call(updateProcessGroups);
+ } else {
+ d3.selectAll('g.process-group').call(updateProcessGroups);
+ }
+ },
+
+ /**
+ * Refreshes the components necessary after a pan event.
+ */
+ pan: function () {
+ d3.selectAll('g.process-group.entering, g.process-group.leaving').call(updateProcessGroups);
+ },
+
+ /**
+ * Reloads the process group state from the server and refreshes the UI.
+ * If the process group is currently unknown, this function reloads the canvas.
+ *
+ * @param {string} id The process group id
+ */
+ reload: function (id) {
+ if (processGroupMap.has(id)) {
+ var processGroupEntity = processGroupMap.get(id);
+ return $.ajax({
+ type: 'GET',
+ url: processGroupEntity.uri,
+ dataType: 'json'
+ }).done(function (response) {
+ nfProcessGroup.set(response);
+ });
+ }
+ },
+
+ /**
+ * Positions the component.
+ *
+ * @param {string} id The id
+ */
+ position: function (id) {
+ d3.select('#id-' + id).call(nfCanvasUtils.position);
+ },
+
+ /**
+ * Removes the specified process group.
+ *
+ * @param {string} processGroupIds The process group id(s)
+ */
+ remove: function (processGroupIds) {
+ var now = new Date().getTime();
+
+ if ($.isArray(processGroupIds)) {
+ $.each(processGroupIds, function (_, processGroupId) {
+ removedCache.set(processGroupId, now);
+ processGroupMap.remove(processGroupId);
+ });
+ } else {
+ removedCache.set(processGroupIds, now);
+ processGroupMap.remove(processGroupIds);
+ }
+
+ // apply the selection and handle all removed process groups
+ select().exit().call(removeProcessGroups);
+ },
+
+ /**
+ * Removes all process groups.
+ */
+ removeAll: function () {
+ nfProcessGroup.remove(processGroupMap.keys());
+ },
+
+ /**
+ * Expires the caches up to the specified timestamp.
+ *
+ * @param timestamp
+ */
+ expireCaches: function (timestamp) {
+ var expire = function (cache) {
+ cache.each(function (entryTimestamp, id) {
+ if (timestamp > entryTimestamp) {
+ cache.remove(id);
+ }
+ });
+ };
+
+ expire(addedCache);
+ expire(removedCache);
+ },
+
+ /**
+ * Enters the specified group.
+ *
+ * @param {string} groupId
+ */
+ enterGroup: function (groupId) {
+
+ // hide the context menu
+ nfContextMenu.hide();
+
+ // set the new group id
+ nfCanvasUtils.setGroupId(groupId);
+
+ // reload the graph
+ return nfCanvasUtils.reload().done(function () {
+
+ // attempt to restore the view
+ var viewRestored = nfCanvasUtils.restoreUserView();
+
+ // if the view was not restore attempt to fit
+ if (viewRestored === false) {
+ nfCanvasUtils.fitCanvas();
+ }
+
+ // update URL deep linking params
+ nfCanvasUtils.setURLParameters(groupId, d3.select());
+
+ }).fail(function () {
+ nfDialog.showOkDialog({
+ headerText: 'Process Group',
+ dialogContent: 'Unable to enter the selected group.'
+ });
+ });
+ }
+ };
+
+ return nfProcessGroup;
+}));
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js
new file mode 100644
index 0000000..8c61dac
--- /dev/null
+++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -0,0 +1,2373 @@
+/*
+ * 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.
+ *
+ * Modifications to the original nifi code for the ONAP project are made
+ * available under the Apache License, Version 2.0
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery',
+ 'Slick',
+ 'd3',
+ 'nf.Client',
+ 'nf.Dialog',
+ 'nf.Storage',
+ 'nf.Common',
+ 'nf.CanvasUtils',
+ 'nf.ControllerServices',
+ 'nf.ErrorHandler',
+ 'nf.FilteredDialogCommon',
+ 'nf.ReportingTask',
+ 'nf.Shell',
+ 'nf.ComponentState',
+ 'nf.ComponentVersion',
+ 'nf.PolicyManagement'],
+ function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) {
+ return (nf.Settings = factory($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement));
+ });
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ module.exports = (nf.Settings =
+ factory(require('jquery'),
+ require('Slick'),
+ require('d3'),
+ require('nf.Client'),
+ require('nf.Dialog'),
+ require('nf.Storage'),
+ require('nf.Common'),
+ require('nf.CanvasUtils'),
+ require('nf.ControllerServices'),
+ require('nf.ErrorHandler'),
+ require('nf.FilteredDialogCommon'),
+ require('nf.ReportingTask'),
+ require('nf.Shell'),
+ require('nf.ComponentState'),
+ require('nf.ComponentVersion'),
+ require('nf.PolicyManagement')));
+ } else {
+ nf.Settings = factory(root.$,
+ root.Slick,
+ root.d3,
+ root.nf.Client,
+ root.nf.Dialog,
+ root.nf.Storage,
+ root.nf.Common,
+ root.nf.CanvasUtils,
+ root.nf.ControllerServices,
+ root.nf.ErrorHandler,
+ root.nf.FilteredDialogCommon,
+ root.nf.ReportingTask,
+ root.nf.Shell,
+ root.nf.ComponentState,
+ root.nf.ComponentVersion,
+ root.nf.PolicyManagement);
+ }
+}(this, function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) {
+ 'use strict';
+
+
+ var config = {
+ urls: {
+ api: '../nifi-api',
+ controllerConfig: '../nifi-api/controller/config',
+ reportingTaskTypes: '../nifi-api/flow/reporting-task-types',
+ createReportingTask: '../nifi-api/controller/reporting-tasks',
+ reportingTasks: '../nifi-api/flow/reporting-tasks',
+ registries: '../nifi-api/controller/registry-clients'
+ }
+ };
+
+ var gridOptions = {
+ forceFitColumns: true,
+ enableTextSelectionOnCells: true,
+ enableCellNavigation: true,
+ enableColumnReorder: false,
+ autoEdit: false,
+ multiSelect: false,
+ rowHeight: 24
+ };
+
+
+ var dcaeDistributorApiHostname;
+
+ //get hostname
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/config',
+ dataType: 'json',
+ contentType: 'application/json',
+ success: function(data){
+ dcaeDistributorApiHostname= data.flowConfiguration.dcaeDistributorApiHostname;
+ console.log(dcaeDistributorApiHostname);
+ }
+ });
+
+
+ /**
+ * Gets the controller services table.
+ *
+ * @returns {*|jQuery|HTMLElement}
+ */
+ var getDistributionEnvironmentsTable = function () {
+ return $('#distribution-environments-table');
+ };
+
+ /**
+ * Gets the controller services table.
+ *
+ * @returns {*|jQuery|HTMLElement}
+ */
+ var getControllerServicesTable = function () {
+ return $('#controller-services-table');
+ };
+
+ /**
+ * Saves the settings for the controller.
+ *
+ * @param version
+ */
+ var saveSettings = function (version) {
+ // marshal the configuration details
+ var configuration = marshalConfiguration();
+ var entity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': version
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': configuration
+ };
+
+ // save the new configuration details
+ $.ajax({
+ type: 'PUT',
+ url: config.urls.controllerConfig,
+ data: JSON.stringify(entity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (response) {
+ // close the settings dialog
+ nfDialog.showOkDialog({
+ headerText: 'Settings',
+ dialogContent: 'Settings successfully applied.'
+ });
+
+ // register the click listener for the save button
+ $('#settings-save').off('click').on('click', function () {
+ saveSettings(response.revision.version);
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+ }
+
+ /**
+ * Initializes the general tab.
+ */
+ var initGeneral = function () {
+ };
+
+ /**
+ * Marshals the details to include in the configuration request.
+ */
+ var marshalConfiguration = function () {
+ // create the configuration
+ var configuration = {};
+ configuration['maxTimerDrivenThreadCount'] = $('#maximum-timer-driven-thread-count-field').val();
+ configuration['maxEventDrivenThreadCount'] = $('#maximum-event-driven-thread-count-field').val();
+ return configuration;
+ };
+
+ /**
+ * Determines if the item matches the filter.
+ *
+ * @param {object} item The item to filter
+ * @param {object} args The filter criteria
+ * @returns {boolean} Whether the item matches the filter
+ */
+ var matchesRegex = function (item, args) {
+ if (args.searchString === '') {
+ return true;
+ }
+
+ try {
+ // perform the row filtering
+ var filterExp = new RegExp(args.searchString, 'i');
+ } catch (e) {
+ // invalid regex
+ return false;
+ }
+
+ // determine if the item matches the filter
+ var matchesLabel = item['label'].search(filterExp) >= 0;
+ var matchesTags = item['tags'].search(filterExp) >= 0;
+ return matchesLabel || matchesTags;
+ };
+
+ /**
+ * Determines if the specified tags match all the tags selected by the user.
+ *
+ * @argument {string[]} tagFilters The tag filters
+ * @argument {string} tags The tags to test
+ */
+ var matchesSelectedTags = function (tagFilters, tags) {
+ var selectedTags = [];
+ $.each(tagFilters, function (_, filter) {
+ selectedTags.push(filter);
+ });
+
+ // normalize the tags
+ var normalizedTags = tags.toLowerCase();
+
+ var matches = true;
+ $.each(selectedTags, function (i, selectedTag) {
+ if (normalizedTags.indexOf(selectedTag) === -1) {
+ matches = false;
+ return false;
+ }
+ });
+
+ return matches;
+ };
+
+ /**
+ * Whether the specified item is selectable.
+ *
+ * @param item reporting task type
+ */
+ var isSelectable = function (item) {
+ return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions);
+ };
+
+ /**
+ * Formatter for the name column.
+ *
+ * @param {type} row
+ * @param {type} cell
+ * @param {type} value
+ * @param {type} columnDef
+ * @param {type} dataContext
+ * @returns {String}
+ */
+ var nameFormatter = function (row, cell, value, columnDef, dataContext) {
+ if (!dataContext.permissions.canRead) {
+ return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+ }
+
+ return nfCommon.escapeHtml(dataContext.component.name);
+ };
+
+ /**
+ * Sorts the specified data using the specified sort details.
+ *
+ * @param {object} sortDetails
+ * @param {object} data
+ */
+ var sort = function (sortDetails, data) {
+ // defines a function for sorting
+ var comparer = function (a, b) {
+ if (a.permissions.canRead && b.permissions.canRead) {
+ if (sortDetails.columnId === 'moreDetails') {
+ var aBulletins = 0;
+ if (!nfCommon.isEmpty(a.bulletins)) {
+ aBulletins = a.bulletins.length;
+ }
+ var bBulletins = 0;
+ if (!nfCommon.isEmpty(b.bulletins)) {
+ bBulletins = b.bulletins.length;
+ }
+ return aBulletins - bBulletins;
+ } else if (sortDetails.columnId === 'type') {
+ var aType = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? nfCommon.substringAfterLast(a.component[sortDetails.columnId], '.') : '';
+ var bType = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? nfCommon.substringAfterLast(b.component[sortDetails.columnId], '.') : '';
+ return aType === bType ? 0 : aType > bType ? 1 : -1;
+ } else if (sortDetails.columnId === 'state') {
+ var aState;
+ if (a.component.validationStatus === 'VALIDATING') {
+ aState = 'Validating';
+ } else if (a.component.validationStatus === 'INVALID') {
+ aState = 'Invalid';
+ } else {
+ aState = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : '';
+ }
+ var bState;
+ if (b.component.validationStatus === 'VALIDATING') {
+ bState = 'Validating';
+ } else if (b.component.validationStatus === 'INVALID') {
+ bState = 'Invalid';
+ } else {
+ bState = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : '';
+ }
+ return aState === bState ? 0 : aState > bState ? 1 : -1;
+ } else {
+ var aString = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : '';
+ var bString = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : '';
+ return aString === bString ? 0 : aString > bString ? 1 : -1;
+ }
+ } else {
+ if (!a.permissions.canRead && !b.permissions.canRead) {
+ return 0;
+ }
+ if (a.permissions.canRead) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ };
+
+ // perform the sort
+ data.sort(comparer, sortDetails.sortAsc);
+ };
+
+ /**
+ * Get the text out of the filter field. If the filter field doesn't
+ * have any text it will contain the text 'filter list' so this method
+ * accounts for that.
+ */
+ var getReportingTaskTypeFilterText = function () {
+ return $('#reporting-task-type-filter').val();
+ };
+
+ /**
+ * Filters the reporting task type table.
+ */
+ var applyReportingTaskTypeFilter = function () {
+ // get the dataview
+ var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
+
+ // ensure the grid has been initialized
+ if (nfCommon.isDefinedAndNotNull(reportingTaskTypesGrid)) {
+ var reportingTaskTypesData = reportingTaskTypesGrid.getData();
+
+ // update the search criteria
+ reportingTaskTypesData.setFilterArgs({
+ searchString: getReportingTaskTypeFilterText()
+ });
+ reportingTaskTypesData.refresh();
+
+ // update the buttons to possibly trigger the disabled state
+ $('#new-reporting-task-dialog').modal('refreshButtons');
+
+ // update the selection if possible
+ if (reportingTaskTypesData.getLength() > 0) {
+ nfFilteredDialogCommon.choseFirstRow(reportingTaskTypesGrid);
+ }
+ }
+ };
+
+ /**
+ * Hides the selected reporting task.
+ */
+ var clearSelectedReportingTask = function () {
+ $('#reporting-task-type-description').attr('title', '').text('');
+ $('#reporting-task-type-name').attr('title', '').text('');
+ $('#reporting-task-type-bundle').attr('title', '').text('');
+ $('#selected-reporting-task-name').text('');
+ $('#selected-reporting-task-type').text('').removeData('bundle');
+ $('#reporting-task-description-container').hide();
+ };
+
+ /**
+ * Clears the selected reporting task type.
+ */
+ var clearReportingTaskSelection = function () {
+ // clear the selected row
+ clearSelectedReportingTask();
+
+ // clear the active cell the it can be reselected when its included
+ var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
+ reportingTaskTypesGrid.resetActiveCell();
+ };
+
+ /**
+ * Performs the filtering.
+ *
+ * @param {object} item The item subject to filtering
+ * @param {object} args Filter arguments
+ * @returns {Boolean} Whether or not to include the item
+ */
+ var filterReportingTaskTypes = function (item, args) {
+ // determine if the item matches the filter
+ var matchesFilter = matchesRegex(item, args);
+
+ // determine if the row matches the selected tags
+ var matchesTags = true;
+ if (matchesFilter) {
+ var tagFilters = $('#reporting-task-tag-cloud').tagcloud('getSelectedTags');
+ var hasSelectedTags = tagFilters.length > 0;
+ if (hasSelectedTags) {
+ matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+ }
+ }
+
+ // determine if the row matches the selected source group
+ var matchesGroup = true;
+ if (matchesFilter && matchesTags) {
+ var bundleGroup = $('#reporting-task-bundle-group-combo').combo('getSelectedOption');
+ if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+ matchesGroup = (item.bundle.group === bundleGroup.value);
+ }
+ }
+
+ // determine if this row should be visible
+ var matches = matchesFilter && matchesTags && matchesGroup;
+
+ // if this row is currently selected and its being filtered
+ if (matches === false && $('#selected-reporting-task-type').text() === item['type']) {
+ clearReportingTaskSelection();
+ }
+
+ return matches;
+ };
+
+ /**
+ * Adds the currently selected reporting task.
+ */
+ var addSelectedReportingTask = function () {
+ var selectedTaskType = $('#selected-reporting-task-type').text();
+ var selectedTaskBundle = $('#selected-reporting-task-type').data('bundle');
+
+ // ensure something was selected
+ if (selectedTaskType === '') {
+ nfDialog.showOkDialog({
+ headerText: 'Settings',
+ dialogContent: 'The type of reporting task to create must be selected.'
+ });
+ } else {
+ addReportingTask(selectedTaskType, selectedTaskBundle);
+ }
+ };
+
+ /**
+ * Adds a new reporting task of the specified type.
+ *
+ * @param {string} reportingTaskType
+ * @param {object} reportingTaskBundle
+ */
+ var addReportingTask = function (reportingTaskType, reportingTaskBundle) {
+ // build the reporting task entity
+ var reportingTaskEntity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': 0
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'type': reportingTaskType,
+ 'bundle': reportingTaskBundle
+ }
+ };
+
+ // add the new reporting task
+ var addTask = $.ajax({
+ type: 'POST',
+ url: config.urls.createReportingTask,
+ data: JSON.stringify(reportingTaskEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (reportingTaskEntity) {
+ // add the item
+ var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance');
+ var reportingTaskData = reportingTaskGrid.getData();
+ reportingTaskData.addItem($.extend({
+ type: 'ReportingTask',
+ bulletins: []
+ }, reportingTaskEntity));
+
+ // resort
+ reportingTaskData.reSort();
+ reportingTaskGrid.invalidate();
+
+ // select the new reporting task
+ var row = reportingTaskData.getRowById(reportingTaskEntity.id);
+ nfFilteredDialogCommon.choseRow(reportingTaskGrid, row);
+ reportingTaskGrid.scrollRowIntoView(row);
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ // hide the dialog
+ $('#new-reporting-task-dialog').modal('hide');
+
+ return addTask;
+ };
+
+ /**
+ * Adds the specified entity.
+ */
+ var addRegistry = function () {
+ var registryEntity = {
+ 'revision': nfClient.getRevision({
+ 'revision': {
+ 'version': 0
+ }
+ }),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'name': $('#registry-name').val(),
+ 'uri': $('#registry-location').val(),
+ 'description': $('#registry-description').val()
+ }
+ };
+
+ // add the new registry
+ var addRegistry = $.ajax({
+ type: 'POST',
+ url: config.urls.registries,
+ data: JSON.stringify(registryEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (registryEntity) {
+ // add the item
+ var registriesGrid = $('#registries-table').data('gridInstance');
+ var registriesData = registriesGrid.getData();
+ registriesData.addItem($.extend({
+ type: 'Registry'
+ }, registryEntity));
+
+ // resort
+ registriesData.reSort();
+ registriesGrid.invalidate();
+
+ // select the new reporting task
+ var row = registriesData.getRowById(registryEntity.id);
+ nfFilteredDialogCommon.choseRow(registriesGrid, row);
+ registriesGrid.scrollRowIntoView(row);
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ // hide the dialog
+ $('#registry-configuration-dialog').modal('hide');
+
+ return addRegistry;
+ };
+
+
+ /**
+ * Adds the specific environment.
+ */
+ var addDistributionEnvironment= function () {
+ var environmentEntity = {
+ 'name': $('#distribution-environment-name').val(),
+ 'runtimeApiUrl': $('#distribution-environment-location').val(),
+ 'description': $('#distribution-environment-description').val()
+// 'nextDistributionTargetId': $('#distribution-environment-nextDistributionTargetId').val()
+ };
+
+ console.log("before POST call ");
+ console.log(environmentEntity);
+
+ // add the new distribution environment
+ var addDistributionEnvironment= $.ajax({
+ type: 'POST',
+ url: dcaeDistributorApiHostname+'/distribution-targets',
+ data: JSON.stringify(environmentEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (environmentEntity) {
+ // add the item
+ console.log("after POST call in response ");
+ console.log(environmentEntity);
+ var environmentsGrid = $('#distribution-environments-table').data('gridInstance');
+ console.log(environmentsGrid);
+ var environmentsData = environmentsGrid.getData();
+ environmentsData.addItem($.extend({
+ type: 'Environment'
+ }, environmentEntity));
+
+
+ // resort
+ environmentsData.reSort();
+ environmentsGrid.invalidate();
+
+ // select the new distribution env.
+ var row = environmentsData.getRowById(environmentEntity.id);
+ nfFilteredDialogCommon.choseRow(environmentsGrid, row);
+ registriesGrid.scrollRowIntoView(row);
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ // hide the dialog
+ $('#distribution-environment-dialog').modal('hide');
+
+ return addDistributionEnvironment;
+ };
+
+
+ /**
+ * Updates the registry with the specified id.
+ *
+ * @param registryId
+ */
+ var updateRegistry = function (registryId) {
+ var registriesGrid = $('#registries-table').data('gridInstance');
+ var registriesData = registriesGrid.getData();
+
+ var registryEntity = registriesData.getItemById(registryId);
+ var requestRegistryEntity = {
+ 'revision': nfClient.getRevision(registryEntity),
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+ 'component': {
+ 'id': registryId,
+ 'name': $('#registry-name').val(),
+ 'uri': $('#registry-location').val(),
+ 'description': $('#registry-description').val()
+ }
+ };
+
+ // add the new reporting task
+ var updateRegistry = $.ajax({
+ type: 'PUT',
+ url: registryEntity.uri,
+ data: JSON.stringify(requestRegistryEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (registryEntity) {
+ // add the item
+ registriesData.updateItem(registryId, $.extend({
+ type: 'Registry'
+ }, registryEntity));
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ // hide the dialog
+ $('#registry-configuration-dialog').modal('hide');
+
+ return updateRegistry;
+ };
+
+
+ /**
+ * Updates the distribution environment with the specified id.
+ *
+ * @param environmentId
+ */
+ var updateDistributionEnvironment = function (environmentId) {
+ var environmentsGrid = $('#distribution-environments-table').data('gridInstance');
+ var environmentsData = environmentsGrid.getData();
+
+ var environmentEntity = environmentsData.getItemById(environmentId);
+ var requestEnvironmentEntity = {
+ 'id': environmentId,
+ 'name': $('#distribution-environment-name').val(),
+ 'runtimeApiUrl': $('#distribution-environment-location').val(),
+ 'description': $('#distribution-environment-description').val(),
+// 'nextDistributionTargetId': $('#distribution-environment-nextDistributionTargetId').val()
+ };
+
+ console.log(requestEnvironmentEntity);
+ // updating distribution environment
+ var updateDistributionEnvironment = $.ajax({
+ type: 'PUT',
+ url: dcaeDistributorApiHostname+'/distribution-targets/'+environmentEntity.id,
+ data: JSON.stringify(requestEnvironmentEntity),
+ dataType: 'json',
+ contentType: 'application/json'
+ }).done(function (environmentEntity) {
+ // update the item
+ console.log(environmentsGrid);
+ environmentsData.updateItem(environmentId, $.extend({
+ type: 'Environment'
+ }, environmentEntity));
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ // hide the dialog
+ $('#distribution-environment-dialog').modal('hide');
+
+ return updateDistributionEnvironment;
+ };
+
+
+
+
+
+ /**
+ * Initializes the new reporting task dialog.
+ */
+ var initNewReportingTaskDialog = function () {
+ // initialize the reporting task type table
+ var reportingTaskTypesColumns = [
+ {
+ id: 'type',
+ name: 'Type',
+ field: 'label',
+ formatter: nfCommon.typeFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'version',
+ name: 'Version',
+ field: 'version',
+ formatter: nfCommon.typeVersionFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'tags',
+ name: 'Tags',
+ field: 'tags',
+ sortable: true,
+ resizable: true,
+ formatter: nfCommon.genericValueFormatter
+ }
+ ];
+
+ // initialize the dataview
+ var reportingTaskTypesData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ reportingTaskTypesData.setItems([]);
+ reportingTaskTypesData.setFilterArgs({
+ searchString: getReportingTaskTypeFilterText()
+ });
+ reportingTaskTypesData.setFilter(filterReportingTaskTypes);
+
+ // initialize the sort
+ nfCommon.sortType({
+ columnId: 'type',
+ sortAsc: true
+ }, reportingTaskTypesData);
+
+ // initialize the grid
+ var reportingTaskTypesGrid = new Slick.Grid('#reporting-task-types-table', reportingTaskTypesData, reportingTaskTypesColumns, gridOptions);
+ reportingTaskTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+ reportingTaskTypesGrid.registerPlugin(new Slick.AutoTooltips());
+ reportingTaskTypesGrid.setSortColumn('type', true);
+ reportingTaskTypesGrid.onSort.subscribe(function (e, args) {
+ nfCommon.sortType({
+ columnId: args.sortCol.field,
+ sortAsc: args.sortAsc
+ }, reportingTaskTypesData);
+ });
+ reportingTaskTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+ if ($.isArray(args.rows) && args.rows.length === 1) {
+ var reportingTaskTypeIndex = args.rows[0];
+ var reportingTaskType = reportingTaskTypesGrid.getDataItem(reportingTaskTypeIndex);
+
+ // set the reporting task type description
+ if (nfCommon.isDefinedAndNotNull(reportingTaskType)) {
+ // show the selected reporting task
+ $('#reporting-task-description-container').show();
+
+ if (nfCommon.isBlank(reportingTaskType.description)) {
+ $('#reporting-task-type-description')
+ .attr('title', '')
+ .html('<span class="unset">No description specified</span>');
+ } else {
+ $('#reporting-task-type-description')
+ .width($('#reporting-task-description-container').innerWidth() - 1)
+ .html(reportingTaskType.description)
+ .ellipsis();
+ }
+
+ var bundle = nfCommon.formatBundle(reportingTaskType.bundle);
+ var type = nfCommon.formatType(reportingTaskType);
+
+ // populate the dom
+ $('#reporting-task-type-name').text(type).attr('title', type);
+ $('#reporting-task-type-bundle').text(bundle).attr('title', bundle);
+ $('#selected-reporting-task-name').text(reportingTaskType.label);
+ $('#selected-reporting-task-type').text(reportingTaskType.type).data('bundle', reportingTaskType.bundle);
+
+ // refresh the buttons based on the current selection
+ $('#new-reporting-task-dialog').modal('refreshButtons');
+ }
+ }
+ });
+ reportingTaskTypesGrid.onDblClick.subscribe(function (e, args) {
+ var reportingTaskType = reportingTaskTypesGrid.getDataItem(args.row);
+
+ if (isSelectable(reportingTaskType)) {
+ addReportingTask(reportingTaskType.type, reportingTaskType.bundle);
+ }
+ });
+ reportingTaskTypesGrid.onViewportChanged.subscribe(function (e, args) {
+ nfCommon.cleanUpTooltips($('#reporting-task-types-table'), 'div.view-usage-restriction');
+ });
+
+ // wire up the dataview to the grid
+ reportingTaskTypesData.onRowCountChanged.subscribe(function (e, args) {
+ reportingTaskTypesGrid.updateRowCount();
+ reportingTaskTypesGrid.render();
+
+ // update the total number of displayed processors
+ $('#displayed-reporting-task-types').text(args.current);
+ });
+ reportingTaskTypesData.onRowsChanged.subscribe(function (e, args) {
+ reportingTaskTypesGrid.invalidateRows(args.rows);
+ reportingTaskTypesGrid.render();
+ });
+ reportingTaskTypesData.syncGridSelection(reportingTaskTypesGrid, true);
+
+ // hold onto an instance of the grid
+ $('#reporting-task-types-table').data('gridInstance', reportingTaskTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+ var usageRestriction = $(this).find('div.view-usage-restriction');
+ if (usageRestriction.length && !usageRestriction.data('qtip')) {
+ var rowId = $(this).find('span.row-id').text();
+
+ // get the status item
+ var item = reportingTaskTypesData.getItemById(rowId);
+
+ // show the tooltip
+ if (item.restricted === true) {
+ var restrictionTip = $('<div></div>');
+
+ if (nfCommon.isBlank(item.usageRestriction)) {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+ } else {
+ restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+ }
+
+ var restrictions = [];
+ if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+ $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+ restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+ });
+ } else {
+ restrictions.push('Access to restricted components regardless of restrictions.');
+ }
+ restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+ usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+ content: restrictionTip,
+ position: {
+ container: $('#summary'),
+ at: 'bottom right',
+ my: 'top left',
+ adjust: {
+ x: 4,
+ y: 4
+ }
+ }
+ }));
+ }
+ }
+ });
+
+ var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+ // load the available reporting tasks
+ $.ajax({
+ type: 'GET',
+ url: config.urls.reportingTaskTypes,
+ dataType: 'json'
+ }).done(function (response) {
+ var id = 0;
+ var tags = [];
+ var groups = d3.set();
+ var restrictedUsage = d3.map();
+ var requiredPermissions = d3.map();
+
+ // begin the update
+ reportingTaskTypesData.beginUpdate();
+
+ // go through each reporting task type
+ $.each(response.reportingTaskTypes, function (i, documentedType) {
+ if (documentedType.restricted === true) {
+ if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+ $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+ var requiredPermission = explicitRestriction.requiredPermission;
+
+ // update required permissions
+ if (!requiredPermissions.has(requiredPermission.id)) {
+ requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(requiredPermission.id)) {
+ restrictedUsage.set(requiredPermission.id, []);
+ }
+
+ restrictedUsage.get(requiredPermission.id).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+ })
+ });
+ } else {
+ // update required permissions
+ if (!requiredPermissions.has(generalRestriction.value)) {
+ requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+ }
+
+ // update component restrictions
+ if (!restrictedUsage.has(generalRestriction.value)) {
+ restrictedUsage.set(generalRestriction.value, []);
+ }
+
+ restrictedUsage.get(generalRestriction.value).push({
+ type: nfCommon.formatType(documentedType),
+ bundle: nfCommon.formatBundle(documentedType.bundle),
+ explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+ });
+ }
+ }
+
+ // record the group
+ groups.add(documentedType.bundle.group);
+
+ // add the documented type
+ reportingTaskTypesData.addItem({
+ id: id++,
+ label: nfCommon.substringAfterLast(documentedType.type, '.'),
+ type: documentedType.type,
+ bundle: documentedType.bundle,
+ description: nfCommon.escapeHtml(documentedType.description),
+ restricted: documentedType.restricted,
+ usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+ explicitRestrictions: documentedType.explicitRestrictions,
+ tags: documentedType.tags.join(', ')
+ });
+
+ // count the frequency of each tag for this type
+ $.each(documentedType.tags, function (i, tag) {
+ tags.push(tag.toLowerCase());
+ });
+ });
+
+ // end the update
+ reportingTaskTypesData.endUpdate();
+
+ // resort
+ reportingTaskTypesData.reSort();
+ reportingTaskTypesGrid.invalidate();
+
+ // set the component restrictions and the corresponding required permissions
+ nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+ // set the total number of processors
+ $('#total-reporting-task-types, #displayed-reporting-task-types').text(response.reportingTaskTypes.length);
+
+ // create the tag cloud
+ $('#reporting-task-tag-cloud').tagcloud({
+ tags: tags,
+ select: applyReportingTaskTypeFilter,
+ remove: applyReportingTaskTypeFilter
+ });
+
+ // build the combo options
+ var options = [{
+ text: 'all groups',
+ value: ''
+ }];
+ groups.each(function (group) {
+ options.push({
+ text: group,
+ value: group
+ });
+ });
+
+ // initialize the bundle group combo
+ $('#reporting-task-bundle-group-combo').combo({
+ options: options,
+ select: applyReportingTaskTypeFilter
+ });
+ }).fail(nfErrorHandler.handleAjaxError);
+
+ var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+ // define the function for filtering the list
+ $('#reporting-task-type-filter').off('keyup').on('keyup', function (e) {
+ var code = e.keyCode ? e.keyCode : e.which;
+
+ // ignore navigation keys
+ if ($.inArray(code, navigationKeys) !== -1) {
+ return;
+ }
+
+ if (code === $.ui.keyCode.ENTER) {
+ var selected = reportingTaskTypesGrid.getSelectedRows();
+
+ if (selected.length > 0) {
+ // grid configured with multi-select = false
+ var item = reportingTaskTypesGrid.getDataItem(selected[0]);
+ if (isSelectable(item)) {
+ addSelectedReportingTask();
+ }
+ }
+ } else {
+ applyReportingTaskTypeFilter();
+ }
+ });
+
+ // setup row navigation
+ nfFilteredDialogCommon.addKeydownListener('#reporting-task-type-filter', reportingTaskTypesGrid, reportingTaskTypesGrid.getData());
+
+ // initialize the reporting task dialog
+ $('#new-reporting-task-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Add Reporting Task',
+ buttons: [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ disabled: function () {
+ var selected = reportingTaskTypesGrid.getSelectedRows();
+
+ if (selected.length > 0) {
+ // grid configured with multi-select = false
+ var item = reportingTaskTypesGrid.getDataItem(selected[0]);
+ return isSelectable(item) === false;
+ } else {
+ return reportingTaskTypesGrid.getData().getLength() === 0;
+ }
+ },
+ handler: {
+ click: function () {
+ addSelectedReportingTask();
+ }
+ }
+ },
+ {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {
+ // clear the selected row
+ clearSelectedReportingTask();
+
+ // clear any filter strings
+ $('#reporting-task-type-filter').val('');
+
+ // clear the tagcloud
+ $('#reporting-task-tag-cloud').tagcloud('clearSelectedTags');
+
+ // reset the group combo
+ $('#reporting-task-bundle-group-combo').combo('setSelectedOption', {
+ value: ''
+ });
+
+ // reset the filter
+ applyReportingTaskTypeFilter();
+
+ // unselect any current selection
+ var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
+ reportingTaskTypesGrid.setSelectedRows([]);
+ reportingTaskTypesGrid.resetActiveCell();
+ },
+ resize: function () {
+ $('#reporting-task-type-description')
+ .width($('#reporting-task-description-container').innerWidth() - 1)
+ .text($('#reporting-task-type-description').attr('title'))
+ .ellipsis();
+ }
+ }
+ });
+
+ // initialize the registry configuration dialog
+ $('#registry-configuration-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ handler: {
+ close: function () {
+ $('#registry-id').text('');
+ $('#registry-name').val('');
+ $('#registry-location').val('');
+ $('#registry-description').val('');
+ }
+ }
+ });
+
+
+ // initialize the distribution environment dialog
+ $('#distribution-environment-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ handler: {
+ close: function () {
+ $('#distribution-environment-id').text('');
+ $('#distribution-environment-name').val('');
+ $('#distribution-environment-location').val('');
+ $('#distribution-environment-description').val('');
+// $('#distribution-environment-nextDistributionTargetId').val('');
+ }
+ }
+ });
+ };
+
+ /**
+ * Initializes the reporting tasks tab.
+ */
+ var initReportingTasks = function () {
+ // initialize the new reporting task dialog
+ initNewReportingTaskDialog();
+
+ var moreReportingTaskDetails = function (row, cell, value, columnDef, dataContext) {
+ if (!dataContext.permissions.canRead) {
+ return '';
+ }
+
+ var markup = '<div title="View Details" class="pointer view-reporting-task fa fa-info-circle"></div>';
+
+ // always include a button to view the usage
+ markup += '<div title="Usage" class="pointer reporting-task-usage fa fa-book"></div>';
+
+ var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors);
+ var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins);
+
+ if (hasErrors) {
+ markup += '<div class="pointer has-errors fa fa-warning" ></div>';
+ }
+
+ if (hasBulletins) {
+ markup += '<div class="has-bulletins fa fa-sticky-note-o"></div>';
+ }
+
+ if (hasErrors || hasBulletins) {
+ markup += '<span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.component.id) + '</span>';
+ }
+
+ return markup;
+ };
+
+ var reportingTaskRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
+ // determine the appropriate label
+ var icon = '', label = '';
+ if (dataContext.status.validationStatus === 'VALIDATING') {
+ icon = 'validating fa fa-spin fa-circle-notch';
+ label = 'Validating';
+ } else if (dataContext.status.validationStatus === 'INVALID') {
+ icon = 'invalid fa fa-warning';
+ label = 'Invalid';
+ } else {
+ if (dataContext.status.runStatus === 'STOPPED') {
+ label = 'Stopped';
+ icon = 'fa fa-stop stopped';
+ } else if (dataContext.status.runStatus === 'RUNNING') {
+ label = 'Running';
+ icon = 'fa fa-play running';
+ } else {
+ label = 'Disabled';
+ icon = 'icon icon-enable-false disabled';
+ }
+ }
+
+ // include the active thread count if appropriate
+ var activeThreadCount = '';
+ if (nfCommon.isDefinedAndNotNull(dataContext.status.activeThreadCount) && dataContext.status.activeThreadCount > 0) {
+ activeThreadCount = '(' + dataContext.status.activeThreadCount + ')';
+ }
+
+ // format the markup
+ var formattedValue = '<div layout="row"><div class="' + icon + '"></div>';
+ return formattedValue + '<div class="status-text">' + nfCommon.escapeHtml(label) + '</div><div style="float: left; margin-left: 4px;">' + nfCommon.escapeHtml(activeThreadCount) + '</div></div>';
+ };
+
+ var reportingTaskActionFormatter = function (row, cell, value, columnDef, dataContext) {
+ var markup = '';
+
+ var canWrite = dataContext.permissions.canWrite;
+ var canRead = dataContext.permissions.canRead;
+ var canOperate = dataContext.operatePermissions.canWrite || canWrite;
+ var isStopped = dataContext.status.runStatus === 'STOPPED';
+
+ if (dataContext.status.runStatus === 'RUNNING') {
+ if (canOperate) {
+ markup += '<div title="Stop" class="pointer stop-reporting-task fa fa-stop"></div>';
+ }
+
+ } else if (isStopped || dataContext.status.runStatus === 'DISABLED') {
+
+ if (canRead && canWrite) {
+ markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil"></div>';
+ }
+
+ // support starting when stopped and no validation errors
+ if (canOperate && dataContext.status.runStatus === 'STOPPED' && dataContext.status.validationStatus === 'VALID') {
+ markup += '<div title="Start" class="pointer start-reporting-task fa fa-play"></div>';
+ }
+
+ if (canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+ markup += '<div title="Change Version" class="pointer change-version-reporting-task fa fa-exchange"></div>';
+ }
+
+ if (canRead && canWrite && nfCommon.canModifyController()) {
+ markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash"></div>';
+ }
+ }
+
+ if (canRead && canWrite && dataContext.component.persistsState === true) {
+ markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks"></div>';
+ }
+
+ // allow policy configuration conditionally
+ if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) {
+ markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>';
+ }
+
+ return markup;
+ };
+
+ // define the column model for the reporting tasks table
+ var reportingTasksColumnModel = [
+ {
+ id: 'moreDetails',
+ name: '&nbsp;',
+ resizable: false,
+ formatter: moreReportingTaskDetails,
+ sortable: true,
+ width: 90,
+ maxWidth: 90,
+ toolTip: 'Sorts based on presence of bulletins'
+ },
+ {
+ id: 'name',
+ name: 'Name',
+ sortable: true,
+ resizable: true,
+ formatter: nameFormatter
+ },
+ {
+ id: 'type',
+ name: 'Type',
+ formatter: nfCommon.instanceTypeFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'bundle',
+ name: 'Bundle',
+ formatter: nfCommon.instanceBundleFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'state',
+ name: 'Run Status',
+ sortable: true,
+ resizeable: true,
+ formatter: reportingTaskRunStatusFormatter
+ }
+ ];
+
+ // action column should always be last
+ reportingTasksColumnModel.push({
+ id: 'actions',
+ name: '&nbsp;',
+ resizable: false,
+ formatter: reportingTaskActionFormatter,
+ sortable: false,
+ width: 90,
+ maxWidth: 90
+ });
+
+ // initialize the dataview
+ var reportingTasksData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ reportingTasksData.setItems([]);
+
+ // initialize the sort
+ sort({
+ columnId: 'name',
+ sortAsc: true
+ }, reportingTasksData);
+
+ // initialize the grid
+ var reportingTasksGrid = new Slick.Grid('#reporting-tasks-table', reportingTasksData, reportingTasksColumnModel, gridOptions);
+ reportingTasksGrid.setSelectionModel(new Slick.RowSelectionModel());
+ reportingTasksGrid.registerPlugin(new Slick.AutoTooltips());
+ reportingTasksGrid.setSortColumn('name', true);
+ reportingTasksGrid.onSort.subscribe(function (e, args) {
+ sort({
+ columnId: args.sortCol.id,
+ sortAsc: args.sortAsc
+ }, reportingTasksData);
+ });
+
+ // configure a click listener
+ reportingTasksGrid.onClick.subscribe(function (e, args) {
+ var target = $(e.target);
+
+ // get the service at this row
+ var reportingTaskEntity = reportingTasksData.getItem(args.row);
+
+ // determine the desired action
+ if (reportingTasksGrid.getColumns()[args.cell].id === 'actions') {
+ if (target.hasClass('edit-reporting-task')) {
+ nfReportingTask.showConfiguration(reportingTaskEntity);
+ } else if (target.hasClass('start-reporting-task')) {
+ nfReportingTask.start(reportingTaskEntity);
+ } else if (target.hasClass('stop-reporting-task')) {
+ nfReportingTask.stop(reportingTaskEntity);
+ } else if (target.hasClass('delete-reporting-task')) {
+ nfReportingTask.promptToDeleteReportingTask(reportingTaskEntity);
+ } else if (target.hasClass('view-state-reporting-task')) {
+ var canClear = reportingTaskEntity.status.runStatus === 'STOPPED' && reportingTaskEntity.status.activeThreadCount === 0;
+ nfComponentState.showState(reportingTaskEntity, canClear);
+ } else if (target.hasClass('change-version-reporting-task')) {
+ nfComponentVersion.promptForVersionChange(reportingTaskEntity);
+ } else if (target.hasClass('edit-access-policies')) {
+ // show the policies for this service
+ nfPolicyManagement.showReportingTaskPolicy(reportingTaskEntity);
+
+ // close the settings dialog
+ $('#shell-close-button').click();
+ }
+ } else if (reportingTasksGrid.getColumns()[args.cell].id === 'moreDetails') {
+ if (target.hasClass('view-reporting-task')) {
+ nfReportingTask.showDetails(reportingTaskEntity);
+ } else if (target.hasClass('reporting-task-usage')) {
+ // close the settings dialog
+ $('#shell-close-button').click();
+
+ // open the documentation for this reporting task
+ nfShell.showPage('../nifi-docs/documentation?' + $.param({
+ select: reportingTaskEntity.component.type,
+ group: reportingTaskEntity.component.bundle.group,
+ artifact: reportingTaskEntity.component.bundle.artifact,
+ version: reportingTaskEntity.component.bundle.version
+ })).done(function () {
+ nfSettings.showSettings();
+ });
+ }
+ }
+ });
+
+ // wire up the dataview to the grid
+ reportingTasksData.onRowCountChanged.subscribe(function (e, args) {
+ reportingTasksGrid.updateRowCount();
+ reportingTasksGrid.render();
+ });
+ reportingTasksData.onRowsChanged.subscribe(function (e, args) {
+ reportingTasksGrid.invalidateRows(args.rows);
+ reportingTasksGrid.render();
+ });
+ reportingTasksData.syncGridSelection(reportingTasksGrid, true);
+
+ // hold onto an instance of the grid
+ $('#reporting-tasks-table').data('gridInstance', reportingTasksGrid).on('mouseenter', 'div.slick-cell', function (e) {
+ var errorIcon = $(this).find('div.has-errors');
+ if (errorIcon.length && !errorIcon.data('qtip')) {
+ var taskId = $(this).find('span.row-id').text();
+
+ // get the task item
+ var reportingTaskEntity = reportingTasksData.getItemById(taskId);
+
+ // format the errors
+ var tooltip = nfCommon.formatUnorderedList(reportingTaskEntity.component.validationErrors);
+
+ // show the tooltip
+ if (nfCommon.isDefinedAndNotNull(tooltip)) {
+ errorIcon.qtip($.extend({},
+ nfCommon.config.tooltipConfig,
+ {
+ content: tooltip,
+ position: {
+ target: 'mouse',
+ viewport: $('#shell-container'),
+ adjust: {
+ x: 8,
+ y: 8,
+ method: 'flipinvert flipinvert'
+ }
+ }
+ }));
+ }
+ }
+
+ var bulletinIcon = $(this).find('div.has-bulletins');
+ if (bulletinIcon.length && !bulletinIcon.data('qtip')) {
+ var taskId = $(this).find('span.row-id').text();
+
+ // get the task item
+ var reportingTaskEntity = reportingTasksData.getItemById(taskId);
+
+ // format the tooltip
+ var bulletins = nfCommon.getFormattedBulletins(reportingTaskEntity.bulletins);
+ var tooltip = nfCommon.formatUnorderedList(bulletins);
+
+ // show the tooltip
+ if (nfCommon.isDefinedAndNotNull(tooltip)) {
+ bulletinIcon.qtip($.extend({},
+ nfCommon.config.tooltipConfig,
+ {
+ content: tooltip,
+ position: {
+ target: 'mouse',
+ viewport: $('#shell-container'),
+ adjust: {
+ x: 8,
+ y: 8,
+ method: 'flipinvert flipinvert'
+ }
+ }
+ }));
+ }
+ }
+ });
+ };
+
+
+
+ /**
+ * Initializing Registry table
+ */
+ var initRegistriesTable = function () {
+
+ var locationFormatter = function (row, cell, value, columnDef, dataContext) {
+ if (!dataContext.permissions.canRead) {
+ return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+ }
+
+ return nfCommon.escapeHtml(dataContext.component.uri);
+ };
+
+ var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
+ if (!dataContext.permissions.canRead) {
+ return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+ }
+
+ return nfCommon.escapeHtml(dataContext.component.description);
+ };
+
+ var registriesActionFormatter = function (row, cell, value, columnDef, dataContext) {
+ var markup = '';
+
+ if (nfCommon.canModifyController()) {
+ // edit registry
+ markup += '<div title="Edit" class="pointer edit-registry fa fa-pencil"></div>';
+
+ // remove registry
+ markup += '<div title="Remove" class="pointer remove-registry fa fa-trash"></div>';
+ }
+
+ return markup;
+ };
+
+ // define the column model for the reporting tasks table
+ var registriesColumnModel = [
+ {
+ id: 'name',
+ name: 'Name',
+ field: 'name',
+ formatter: nameFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'uri',
+ name: 'Location',
+ field: 'uri',
+ formatter: locationFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'description',
+ name: 'Description',
+ field: 'description',
+ formatter: descriptionFormatter,
+ sortable: true,
+ resizable: true
+ }
+ ];
+
+ // action column should always be last
+ registriesColumnModel.push({
+ id: 'actions',
+ name: '&nbsp;',
+ resizable: false,
+ formatter: registriesActionFormatter,
+ sortable: false,
+ width: 90,
+ maxWidth: 90
+ });
+
+ // initialize the dataview
+ var registriesData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ registriesData.setItems([]);
+
+ // initialize the sort
+ sort({
+ columnId: 'name',
+ sortAsc: true
+ }, registriesData);
+
+ // initialize the grid
+ var registriesGrid = new Slick.Grid('#registries-table', registriesData, registriesColumnModel, gridOptions);
+ registriesGrid.setSelectionModel(new Slick.RowSelectionModel());
+ registriesGrid.registerPlugin(new Slick.AutoTooltips());
+ registriesGrid.setSortColumn('name', true);
+ registriesGrid.onSort.subscribe(function (e, args) {
+ sort({
+ columnId: args.sortCol.id,
+ sortAsc: args.sortAsc
+ }, registriesData);
+ });
+
+ // configure a click listener
+ registriesGrid.onClick.subscribe(function (e, args) {
+ var target = $(e.target);
+
+ // get the service at this row
+ var registryEntity = registriesData.getItem(args.row);
+
+ // determine the desired action
+ if (registriesGrid.getColumns()[args.cell].id === 'actions') {
+ if (target.hasClass('edit-registry')) {
+ editRegistry(registryEntity);
+ } else if (target.hasClass('remove-registry')) {
+ promptToRemoveRegistry(registryEntity);
+ }
+ } else if (registriesGrid.getColumns()[args.cell].id === 'moreDetails') {
+ // if (target.hasClass('view-reporting-task')) {
+ // nfReportingTask.showDetails(reportingTaskEntity);
+ // } else if (target.hasClass('reporting-task-usage')) {
+ // // close the settings dialog
+ // $('#shell-close-button').click();
+ //
+ // // open the documentation for this reporting task
+ // nfShell.showPage('../nifi-docs/documentation?' + $.param({
+ // select: reportingTaskEntity.component.type,
+ // group: reportingTaskEntity.component.bundle.group,
+ // artifact: reportingTaskEntity.component.bundle.artifact,
+ // version: reportingTaskEntity.component.bundle.version
+ // })).done(function () {
+ // nfSettings.showSettings();
+ // });
+ // }
+ }
+ });
+
+ // wire up the dataview to the grid
+ registriesData.onRowCountChanged.subscribe(function (e, args) {
+ registriesGrid.updateRowCount();
+ registriesGrid.render();
+ });
+ registriesData.onRowsChanged.subscribe(function (e, args) {
+ registriesGrid.invalidateRows(args.rows);
+ registriesGrid.render();
+ });
+ registriesData.syncGridSelection(registriesGrid, true);
+
+ // hold onto an instance of the grid
+ $('#registries-table').data('gridInstance', registriesGrid);
+ };
+
+
+
+
+ /**
+ * Initializing Distribution Environments table
+ */
+ var initDistributionEnvironmentsTable = function () {
+
+ var locationFormatter = function (row, cell, value, columnDef, dataContext) {
+// if (!dataContext.permissions.canRead) {
+ return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+// }
+
+ return nfCommon.escapeHtml(dataContext.component.uri);
+ };
+
+ var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
+// if (!dataContext.permissions.canRead) {
+ return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+// }
+
+ return nfCommon.escapeHtml(dataContext.component.description);
+ };
+
+ var envActionFormatter = function (row, cell, value, columnDef, dataContext) {
+ var markup = '';
+
+ if (nfCommon.canModifyController()) {
+ // edit environment
+ markup += '<div title="Edit" class="pointer edit-registry fa fa-pencil"></div>';
+
+ // remove environment
+ markup += '<div title="Remove" class="pointer remove-registry fa fa-trash"></div>';
+ }
+
+ return markup;
+ };
+
+ // define the column model for the Distribution environments table
+ var environmentsColumnModel = [
+ {
+ id: 'name',
+ name: 'Name',
+ field: 'name',
+// formatter: nameFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'runtimeApiUrl',
+ name: 'Location',
+ field: 'runtimeApiUrl',
+// formatter: locationFormatter,
+ sortable: true,
+ resizable: true
+ },
+ {
+ id: 'description',
+ name: 'Description',
+ field: 'description',
+// formatter: descriptionFormatter,
+ sortable: true,
+ resizable: true
+ }
+// ,
+// {
+// id: 'nextDistributionTargetId',
+// name: 'Next Distribution TargetId',
+// field: 'nextDistributionTargetId',
+// formatter: descriptionFormatter,
+// sortable: true,
+// resizable: true
+// }
+ ];
+
+ // action column should always be last
+ environmentsColumnModel.push({
+ id: 'actions',
+ name: '&nbsp;',
+ resizable: false,
+ formatter: envActionFormatter,
+ sortable: false,
+ width: 90,
+ maxWidth: 90
+ });
+
+ // initialize the dataview
+ var environmentsData = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ environmentsData.setItems([]);
+
+ // initialize the sort
+ sort({
+ columnId: 'name',
+ sortAsc: true
+ }, environmentsData);
+
+ // initialize the grid
+ var environmentsGrid = new Slick.Grid('#distribution-environments-table', environmentsData, environmentsColumnModel, gridOptions);
+ environmentsGrid.setSelectionModel(new Slick.RowSelectionModel());
+ environmentsGrid.registerPlugin(new Slick.AutoTooltips());
+ environmentsGrid.setSortColumn('name', true);
+ environmentsGrid.onSort.subscribe(function (e, args) {
+ sort({
+ columnId: args.sortCol.id,
+ sortAsc: args.sortAsc
+ }, environmentsData);
+ });
+
+ // configure a click listener
+ environmentsGrid.onClick.subscribe(function (e, args) {
+ var target = $(e.target);
+
+ // get the service at this row
+ var environmentEntity = environmentsData.getItem(args.row);
+
+ // determine the desired action
+ if (environmentsGrid.getColumns()[args.cell].id === 'actions') {
+ if (target.hasClass('edit-registry')) { //left it as edit-registry intentionally to inherit styles
+ editDistributionEnvironment(environmentEntity);
+ } else if (target.hasClass('remove-registry')) { //left it as remove-registry intentionally to inherit styles
+ promptToRemoveDistributionEnvironment(environmentEntity);
+ }
+ } else if (environmentsGrid.getColumns()[args.cell].id === 'moreDetails') { }
+ });
+
+ // wire up the dataview to the grid
+ environmentsData.onRowCountChanged.subscribe(function (e, args) {
+ environmentsGrid.updateRowCount();
+ environmentsGrid.render();
+ });
+ environmentsData.onRowsChanged.subscribe(function (e, args) {
+ environmentsGrid.invalidateRows(args.rows);
+ environmentsGrid.render();
+ });
+ environmentsData.syncGridSelection(environmentsGrid, true);
+
+ // hold onto an instance of the grid
+ $('#distribution-environments-table').data('gridInstance', environmentsGrid);
+ };
+
+
+
+ /**
+ * Edits the specified registry entity.
+ *
+ * @param registryEntity
+ */
+ var editRegistry = function (registryEntity) {
+ // populate the dialog
+ $('#registry-id').text(registryEntity.id);
+ $('#registry-name').val(registryEntity.component.name);
+ $('#registry-location').val(registryEntity.component.uri);
+ $('#registry-description').val(registryEntity.component.description);
+
+ // show the dialog
+ $('#registry-configuration-dialog').modal('setHeaderText', 'Edit Registry Client').modal('setButtonModel', [{
+ buttonText: 'Update',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ updateRegistry(registryEntity.id);
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+ };
+
+
+ /**
+ * Edits the specified distribution environment entity.
+ *@author Renu
+ * @param distributionEnvironmentEntity
+ */
+ var editDistributionEnvironment = function (distributionEnvironmentEntity) {
+ // populate the dialog
+ $('#distribution-environment-id').text(distributionEnvironmentEntity.id);
+ $('#distribution-environment-name').val(distributionEnvironmentEntity.name);
+ $('#distribution-environment-location').val(distributionEnvironmentEntity.runtimeApiUrl);
+ $('#distribution-environment-description').val(distributionEnvironmentEntity.description);
+// $('#distribution-environment-nextDistributionTargetId ').val(distributionEnvironmentEntity.component.description);
+
+ // show the dialog
+ $('#distribution-environment-dialog').modal('setHeaderText', 'Edit Environment').modal('setButtonModel', [{
+ buttonText: 'Update',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ updateDistributionEnvironment(distributionEnvironmentEntity.id);
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+ };
+
+
+ /**
+ * Prompts the user before attempting to delete the specified environment.
+ *
+ * @param {object} environmentEntity
+ */
+ var promptToRemoveDistributionEnvironment = function (environmentEntity) {
+ // prompt for deletion
+ nfDialog.showYesNoDialog({
+ headerText: 'Delete Environment',
+ dialogContent: 'Delete Environment \'' + nfCommon.escapeHtml(environmentEntity.name) + '\'?',
+ yesHandler: function () {
+ removeDistributionEnvironment(environmentEntity);
+ }
+ });
+ };
+
+
+ /**
+ * Deletes the specified environment.
+ *
+ * @param {object} environmentEntity
+ */
+ var removeDistributionEnvironment = function (environmentEntity) {
+ console.log(environmentEntity);
+ $.ajax({
+ type: 'DELETE',
+ url: dcaeDistributorApiHostname+'/distribution-targets/'+environmentEntity.id,
+ dataType: 'json'
+ }).done(function (response) {
+ console.log(response);
+ // remove the task
+ var environmentsGrid = $('#distribution-environments-table').data('gridInstance');
+ console.log(environmentsGrid);
+ var environmentsData = environmentsGrid.getData();
+ environmentsData.deleteItem(environmentEntity.id);
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+
+ /**
+ * Prompts the user before attempting to delete the specified registry.
+ *
+ * @param {object} registryEntity
+ */
+ var promptToRemoveRegistry = function (registryEntity) {
+ // prompt for deletion
+ nfDialog.showYesNoDialog({
+ headerText: 'Delete Registry',
+ dialogContent: 'Delete registry \'' + nfCommon.escapeHtml(registryEntity.component.name) + '\'?',
+ yesHandler: function () {
+ removeRegistry(registryEntity);
+ }
+ });
+ };
+
+ /**
+ * Deletes the specified registry.
+ *
+ * @param {object} registryEntity
+ */
+ var removeRegistry = function (registryEntity) {
+ var revision = nfClient.getRevision(registryEntity);
+ $.ajax({
+ type: 'DELETE',
+ url: registryEntity.uri + '?' + $.param({
+ 'version': revision.version,
+ 'clientId': revision.clientId,
+ 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+ }),
+ dataType: 'json'
+ }).done(function (response) {
+ // remove the task
+ var registryGrid = $('#registries-table').data('gridInstance');
+ var registryData = registryGrid.getData();
+ registryData.deleteItem(registryEntity.id);
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Loads the settings.
+ */
+ var loadSettings = function () {
+ var setUnauthorizedText = function () {
+ $('#read-only-maximum-timer-driven-thread-count-field').addClass('unset').text('Unauthorized');
+ $('#read-only-maximum-event-driven-thread-count-field').addClass('unset').text('Unauthorized');
+ };
+
+ var setEditable = function (editable) {
+ if (editable) {
+ $('#general-settings div.editable').show();
+ $('#general-settings div.read-only').hide();
+ $('#settings-save').show();
+ } else {
+ $('#general-settings div.editable').hide();
+ $('#general-settings div.read-only').show();
+ $('#settings-save').hide();
+ }
+ };
+
+ var settings = $.Deferred(function (deferred) {
+ $.ajax({
+ type: 'GET',
+ url: config.urls.controllerConfig,
+ dataType: 'json'
+ }).done(function (response) {
+ if (response.permissions.canWrite) {
+ // populate the settings
+ $('#maximum-timer-driven-thread-count-field').removeClass('unset').val(response.component.maxTimerDrivenThreadCount);
+ $('#maximum-event-driven-thread-count-field').removeClass('unset').val(response.component.maxEventDrivenThreadCount);
+
+ setEditable(true);
+
+ // register the click listener for the save button
+ $('#settings-save').off('click').on('click', function () {
+ saveSettings(response.revision.version);
+ });
+ } else {
+ if (response.permissions.canRead) {
+ // populate the settings
+ $('#read-only-maximum-timer-driven-thread-count-field').removeClass('unset').text(response.component.maxTimerDrivenThreadCount);
+ $('#read-only-maximum-event-driven-thread-count-field').removeClass('unset').text(response.component.maxEventDrivenThreadCount);
+ } else {
+ setUnauthorizedText();
+ }
+
+ setEditable(false);
+ }
+ deferred.resolve();
+ }).fail(function (xhr, status, error) {
+ if (xhr.status === 403) {
+ setUnauthorizedText();
+ setEditable(false);
+ deferred.resolve();
+ } else {
+ deferred.reject(xhr, status, error);
+ }
+ });
+ }).promise();
+
+ // load the controller services
+ var controllerServicesUri = config.urls.api + '/flow/controller/controller-services';
+ var controllerServicesXhr = nfControllerServices.loadControllerServices(controllerServicesUri, getControllerServicesTable());
+
+ // load the reporting tasks
+ var reportingTasks = loadReportingTasks();
+
+ // load the registries
+ var registries = loadRegistries();
+
+ // load the distribution environments
+ var distributionEnvironments = loadDistributionEnvironments();
+
+ // return a deferred for all parts of the settings
+ return $.when(settings, controllerServicesXhr, reportingTasks).done(function (settingsResult, controllerServicesResult) {
+ var controllerServicesResponse = controllerServicesResult[0];
+
+ // update the current time
+ $('#settings-last-refreshed').text(controllerServicesResponse.currentTime);
+ }).fail(nfErrorHandler.handleAjaxError);
+ };
+
+ /**
+ * Loads the reporting tasks.
+ */
+ var loadReportingTasks = function () {
+ return $.ajax({
+ type: 'GET',
+ url: config.urls.reportingTasks,
+ dataType: 'json'
+ }).done(function (response) {
+ var tasks = [];
+ $.each(response.reportingTasks, function (_, task) {
+ tasks.push($.extend({
+ type: 'ReportingTask',
+ bulletins: []
+ }, task));
+ });
+
+ var reportingTasksElement = $('#reporting-tasks-table');
+ nfCommon.cleanUpTooltips(reportingTasksElement, 'div.has-errors');
+ nfCommon.cleanUpTooltips(reportingTasksElement, 'div.has-bulletins');
+
+ var reportingTasksGrid = reportingTasksElement.data('gridInstance');
+ var reportingTasksData = reportingTasksGrid.getData();
+
+ // update the reporting tasks
+ reportingTasksData.setItems(tasks);
+ reportingTasksData.reSort();
+ reportingTasksGrid.invalidate();
+ });
+ };
+
+ /**
+ * Loads the registries.
+ */
+ var loadRegistries = function () {
+ return $.ajax({
+ type: 'GET',
+ url: config.urls.registries,
+ dataType: 'json'
+ }).done(function (response) {
+ var registries = [];
+ $.each(response.registries, function (_, registryEntity) {
+ registries.push($.extend({
+ type: 'Registry'
+ }, registryEntity));
+ });
+
+ var registriesGrid = $('#registries-table').data('gridInstance');
+ var registriesData = registriesGrid.getData();
+
+ // update the registries
+ registriesData.setItems(registries);
+ registriesData.reSort();
+ registriesGrid.invalidate();
+ });
+ };
+
+ /**
+ * Loads the distribution environments.
+ */
+ var loadDistributionEnvironments = function(){
+ console.log("in loadDistributionEnvironments.. ");
+ return $.ajax({
+ type: 'GET',
+ url: dcaeDistributorApiHostname+'/distribution-targets',
+ dataType: 'json'
+ }).done(function (response) {
+ console.log(response);
+ var environments = [];
+ $.each(response.distributionTargets, function (_, environmentEntity) {
+ console.log(environmentEntity);
+ environments.push($.extend({
+ type: 'Environment'
+ }, environmentEntity));
+ });
+
+ console.log(environments);
+ var environmentsGrid = $('#distribution-environments-table').data('gridInstance');
+ console.log(environmentsGrid);
+ var environmentsData = environmentsGrid.getData();
+
+ // update the distribution environments
+ environmentsData.setItems(environments);
+ environmentsData.reSort();
+ environmentsGrid.invalidate();
+ });
+ };
+
+ /**
+ * Shows the process group configuration.
+ */
+ var showSettings = function () {
+ // show the settings dialog
+ nfShell.showContent('#settings').done(function () {
+ reset();
+ });
+
+ //reset content to account for possible policy changes
+ $('#settings-tabs').find('.selected-tab').click();
+
+ // adjust the table size
+ nfSettings.resetTableSize();
+ };
+
+ /**
+ * Reset state of this dialog.
+ */
+ var reset = function () {
+ // reset button state
+ $('#settings-save').mouseout();
+ };
+
+ var nfSettings = {
+ /**
+ * Initializes the settings page.
+ */
+ init: function () {
+ // initialize the settings tabs
+ $('#settings-tabs').tabbs({
+ tabStyle: 'tab',
+ selectedTabStyle: 'selected-tab',
+ scrollableTabContentStyle: 'scrollable',
+ tabs: [{
+ name: 'General',
+ tabContentId: 'general-settings-tab-content'
+ }, {
+ name: 'Reporting Task Controller Services',
+ tabContentId: 'controller-services-tab-content'
+ }, {
+ name: 'Reporting Tasks',
+ tabContentId: 'reporting-tasks-tab-content'
+ }, {
+ name: 'Registry Clients',
+ tabContentId: 'registries-tab-content'
+ },{
+ name: 'Distribution Target Environments',
+ tabContentId: 'distribution-environment-content'
+ }
+ ],
+ select: function () {
+ var tab = $(this).text();
+ if (tab === 'General') {
+ $('#controller-cs-availability').hide();
+ $('#new-service-or-task').hide();
+ $('#settings-save').show();
+ } else {
+ var canModifyController = false;
+ if (nfCommon.isDefinedAndNotNull(nfCommon.currentUser)) {
+ // only consider write permissions for creating new controller services/reporting tasks
+ canModifyController = nfCommon.currentUser.controllerPermissions.canWrite === true;
+ }
+
+ if (canModifyController) {
+ $('#new-service-or-task').show();
+ $('div.controller-settings-table').css('top', '32px');
+
+ // update the tooltip on the button
+ $('#new-service-or-task').attr('title', function () {
+ if (tab === 'Reporting Task Controller Services') {
+ $('#settings-save').hide();
+ return 'Create a new reporting task controller service';
+ } else if (tab === 'Reporting Tasks') {
+ $('#settings-save').hide();
+ return 'Create a new reporting task';
+ } else if (tab === 'Registry Clients') {
+ $('#settings-save').hide();
+ return 'Register a new registry client';
+ }else if (tab === 'Distribution Target Environments') {
+ console.log("in env tab...");
+ $('#settings-save').hide();
+ return 'Add a new distribution environment';
+ }
+ });
+ } else {
+ $('#new-service-or-task').hide();
+ $('div.controller-settings-table').css('top', '0');
+ }
+
+ if (tab === 'Reporting Task Controller Services') {
+ $('#controller-cs-availability').show();
+ } else if (tab === 'Reporting Tasks' || tab === 'Registry Clients'|| tab === 'Distribution Target Environments') {
+ $('#controller-cs-availability').hide();
+ }
+
+ // resize the table
+ nfSettings.resetTableSize();
+ }
+ }
+ });
+
+ // settings refresh button
+ $('#settings-refresh-button').click(function () {
+ loadSettings();
+ });
+
+ // create a new controller service or reporting task
+ $('#new-service-or-task').on('click', function () {
+ console.log("on Add Buttton clicked");
+ var selectedTab = $('#settings-tabs li.selected-tab').text();
+ if (selectedTab === 'Reporting Task Controller Services') {
+ var controllerServicesUri = config.urls.api + '/controller/controller-services';
+ nfControllerServices.promptNewControllerService(controllerServicesUri, getControllerServicesTable());
+ } else if (selectedTab === 'Reporting Tasks') {
+ $('#new-reporting-task-dialog').modal('show');
+
+ var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
+ if (nfCommon.isDefinedAndNotNull(reportingTaskTypesGrid)) {
+ var reportingTaskTypesData = reportingTaskTypesGrid.getData();
+
+ // reset the canvas size after the dialog is shown
+ reportingTaskTypesGrid.resizeCanvas();
+
+ // select the first row if possible
+ if (reportingTaskTypesData.getLength() > 0) {
+ nfFilteredDialogCommon.choseFirstRow(reportingTaskTypesGrid);
+ }
+ }
+
+ // set the initial focus
+ $('#reporting-task-type-filter').focus();
+ } else if (selectedTab === 'Registry Clients') {
+ $('#registry-configuration-dialog').modal('setHeaderText', 'Add Registry Client').modal('setButtonModel', [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ addRegistry();
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+
+ // set the initial focus
+ $('#registry-name').focus();
+ } else if (selectedTab === 'Distribution Target Environments') {
+ $('#distribution-environment-dialog').modal('setHeaderText', 'Add New Environment').modal('setButtonModel', [{
+ buttonText: 'Add',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ addDistributionEnvironment();
+ }
+ }
+ }, {
+ buttonText: 'Cancel',
+ color: {
+ base: '#E3E8EB',
+ hover: '#C7D2D7',
+ text: '#004849'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }]).modal('show');
+
+ $(window).resize(function() {
+ var x=0;
+ console.log(x);
+ $("#distribution-environment-content").text(x= x + 1);
+ });
+
+ $(window).resize(function() {
+ var x=0;
+ console.log(x);
+ $("#distribution-environments-table").text(x= x + 1);
+ console.log(x);
+ console.log("resizing...table...");
+ });
+ // set the initial focus
+ $('#distribution-environment-name').focus();
+ }
+ });
+
+ // initialize each tab
+ initGeneral();
+ nfControllerServices.init(getControllerServicesTable(), nfSettings.showSettings);
+ initReportingTasks();
+ initRegistriesTable();
+ initDistributionEnvironmentsTable();
+ },
+
+ /**
+ * Update the size of the grid based on its container's current size.
+ */
+ resetTableSize: function () {
+ nfControllerServices.resetTableSize(getControllerServicesTable());
+ nfControllerServices.resetTableSize(getDistributionEnvironmentsTable());
+
+ var reportingTasksGrid = $('#reporting-tasks-table').data('gridInstance');
+ if (nfCommon.isDefinedAndNotNull(reportingTasksGrid)) {
+ reportingTasksGrid.resizeCanvas();
+ }
+ },
+
+ /**
+ * Shows the settings dialog.
+ */
+ showSettings: function () {
+ return loadSettings().done(showSettings);
+ },
+
+ /**
+ * Loads the settings dialogs.
+ */
+ loadSettings: function () {
+ return loadSettings();
+ },
+
+ /**
+ * Selects the specified controller service.
+ *
+ * @param {string} controllerServiceId
+ */
+ selectControllerService: function (controllerServiceId) {
+ var controllerServiceGrid = getControllerServicesTable().data('gridInstance');
+ var controllerServiceData = controllerServiceGrid.getData();
+
+ // select the desired service
+ var row = controllerServiceData.getRowById(controllerServiceId);
+ nfFilteredDialogCommon.choseRow(controllerServiceGrid, row);
+ controllerServiceGrid.scrollRowIntoView(row);
+
+ // select the controller services tab
+ $('#settings-tabs').find('li:eq(1)').click();
+ },
+
+ /**
+ * Selects the specified reporting task.
+ *
+ * @param {string} reportingTaskId
+ */
+ selectReportingTask: function (reportingTaskId) {
+ var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance');
+ var reportingTaskData = reportingTaskGrid.getData();
+
+ // select the desired service
+ var row = reportingTaskData.getRowById(reportingTaskId);
+ nfFilteredDialogCommon.choseRow(reportingTaskGrid, row);
+ reportingTaskGrid.scrollRowIntoView(row);
+
+ // select the controller services tab
+ $('#settings-tabs').find('li:eq(2)').click();
+ },
+
+ /**
+ * Sets the controller service and reporting task bulletins in their respective tables.
+ *
+ * @param {object} controllerServiceBulletins
+ * @param {object} reportingTaskBulletins
+ */
+ setBulletins: function (controllerServiceBulletins, reportingTaskBulletins) {
+ if ($('#controller-services-table').data('gridInstance')) {
+ nfControllerServices.setBulletins(getControllerServicesTable(), controllerServiceBulletins);
+ }
+
+ // reporting tasks
+ var reportingTasksGrid = $('#reporting-tasks-table').data('gridInstance');
+ var reportingTasksData = reportingTasksGrid.getData();
+ reportingTasksData.beginUpdate();
+
+ // if there are some bulletins process them
+ if (!nfCommon.isEmpty(reportingTaskBulletins)) {
+ var reportingTaskBulletinsBySource = d3.nest()
+ .key(function (d) {
+ return d.sourceId;
+ })
+ .map(reportingTaskBulletins, d3.map);
+
+ reportingTaskBulletinsBySource.each(function (sourceBulletins, sourceId) {
+ var reportingTask = reportingTasksData.getItemById(sourceId);
+ if (nfCommon.isDefinedAndNotNull(reportingTask)) {
+ reportingTasksData.updateItem(sourceId, $.extend(reportingTask, {
+ bulletins: sourceBulletins
+ }));
+ }
+ });
+ } else {
+ // if there are no bulletins clear all
+ var reportingTasks = reportingTasksData.getItems();
+ $.each(reportingTasks, function (_, reportingTask) {
+ reportingTasksData.updateItem(reportingTask.id, $.extend(reportingTask, {
+ bulletins: []
+ }));
+ });
+ }
+ reportingTasksData.endUpdate();
+ }
+ };
+
+ return nfSettings;
+}));
diff --git a/mod/designtool/nifi-war-to-jar/extract.sh b/mod/designtool/nifi-war-to-jar/extract.sh
new file mode 100755
index 0000000..99e8621
--- /dev/null
+++ b/mod/designtool/nifi-war-to-jar/extract.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# ==============================================================================
+# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+# ==============================================================================
+# 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.
+# ============LICENSE_END=======================================================
+
+#
+# The nifi web api is available as a war file but not as a jar file.
+# We need to compile patches against the classes in it, so we need
+# a jar file. This shell extracts the class files from the war and copies
+# them to target/classes so maven can package them into a jar file.
+# The jar is then used as a "provided" dependency for compiling the
+# design tool patches
+#
+
+set -euf -o pipefail
+echo Extracting classes from "$1"
+cd target
+rm -rf WEB-INF classes
+jar xf $1 WEB-INF/classes/org/apache/nifi
+mv WEB-INF/classes .
+rmdir WEB-INF
diff --git a/mod/designtool/nifi-war-to-jar/pom.xml b/mod/designtool/nifi-war-to-jar/pom.xml
new file mode 100644
index 0000000..03a5074
--- /dev/null
+++ b/mod/designtool/nifi-war-to-jar/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<!--
+================================================================================
+Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+================================================================================
+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.
+============LICENSE_END=========================================================
+
+-->
+<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.onap.dcaegen2.platform.mod</groupId>
+ <artifactId>designtool</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-war-to-jar</artifactId>
+ <name>dcaegen2-platform-mod-designtool-nifi-web-api-war-to-jar</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-web-api</artifactId>
+ <version>${nifi.version}</version>
+ <type>war</type>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <includeProjectDependencies>false</includeProjectDependencies>
+ <includePluginDependencies>true</includePluginDependencies>
+ <executable>./extract.sh</executable>
+ <arguments>
+ <argument>${env.HOME}/.m2/repository/org/apache/nifi/nifi-web-api/${nifi.version}/nifi-web-api-${nifi.version}.war</argument>
+ </arguments>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/mod/designtool/pom.xml b/mod/designtool/pom.xml
new file mode 100644
index 0000000..330b230
--- /dev/null
+++ b/mod/designtool/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!--
+================================================================================
+Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+================================================================================
+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.
+============LICENSE_END=========================================================
+
+-->
+<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.onap.oparent</groupId>
+ <artifactId>oparent</artifactId>
+ <version>2.0.0</version>
+ </parent>
+ <groupId>org.onap.dcaegen2.platform.mod</groupId>
+ <artifactId>designtool</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>dcaegen2-platform-mod-designtool-parent</name>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <nifi.version>1.9.2</nifi.version>
+ <jetty.version>9.4.11.v20180605</jetty.version>
+ <org.slf4j.version>1.7.25</org.slf4j.version>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ <staging.dir>${project.build.directory}/mp</staging.dir>
+ <maven.build.timestamp.format>yyyyMMdd'T'HHmmss</maven.build.timestamp.format>
+ <docker.fabric.version>0.32.0</docker.fabric.version>
+ <sonar.coverage.jacoco.xmlReportPaths>${project.reporting.outputDirectory}/jacoco-ut/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
+ </properties>
+ <modules>
+ <module>nifi-war-to-jar</module>
+ <module>designtool-web</module>
+ </modules>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonarsource.scanner.maven</groupId>
+ <artifactId>sonar-maven-plugin</artifactId>
+ <version>3.6.0.1398</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>