NIFI-13732 Initial commit of nifi-api 2.0.0-SNAPSHOT
diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 0000000..6022a38 --- /dev/null +++ b/.asf.yaml
@@ -0,0 +1,26 @@ +# SPDX-License-Identifier: Apache-2.0 + +github: + description: "Apache NiFi API" + homepage: https://nifi.apache.org + labels: + - apache + - nifi + features: + wiki: false + issues: false + projects: false + enabled_merge_buttons: + squash: true + autolink_jira: + - NIFI + - NIP + protected_branches: + main: + required_signatures: true + required_linear_history: true +notifications: + commits: commits@nifi.apache.org + issues: issues@nifi.apache.org + pullrequests: issues@nifi.apache.org + jira_options: link worklog
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2a1ef8e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,23 @@ +<!-- SPDX-License-Identifier: Apache-2.0 --> + +# Summary + +[NIFI-00000](https://issues.apache.org/jira/browse/NIFI-00000) + +# Tracking + +Please complete the following tracking steps prior to pull request creation. + +### Issue Tracking + +- [ ] [Apache NiFi Jira](https://issues.apache.org/jira/browse/NIFI) issue created + +### Pull Request Tracking + +- [ ] Pull Request title starts with Apache NiFi Jira issue number, such as `NIFI-00000` +- [ ] Pull Request commit message starts with Apache NiFi Jira issue number, as such `NIFI-00000` + +### Pull Request Formatting + +- [ ] Pull Request based on current revision of the `main` branch +- [ ] Pull Request refers to a feature branch with one commit containing changes
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b9f7518 --- /dev/null +++ b/.github/workflows/build.yml
@@ -0,0 +1,28 @@ +# SPDX-License-Identifier: Apache-2.0 + +name: build + +on: + push: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + build-java-21: + name: Java 21 + runs-on: ubuntu-latest + steps: + - name: Checkout Sources + uses: actions/checkout@v4 + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'liberica' + cache: 'maven' + - name: Build + run: ./mvnw --batch-mode verify
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1606a4 --- /dev/null +++ b/.gitignore
@@ -0,0 +1,35 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Editors +.idea/ +.vscode/ +.java-version +.project +.settings +.classpath +nbactions.xml +nb-configuration.xml +.DS_Store +.metadata +.recommenders +*.iml +*.iws +*.ipr +*~ + +# Binaries +*.class +target/ + +# Packages +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Logs +*.log +hs_err_pid*
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE
@@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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.
diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bc8e448 --- /dev/null +++ b/NOTICE
@@ -0,0 +1,5 @@ +Apache NiFi API +Copyright 2024 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a4ae23 --- /dev/null +++ b/README.md
@@ -0,0 +1,59 @@ +<!-- SPDX-License-Identifier: Apache-2.0 --> + +# Apache NiFi API + +[](https://github.com/apache/nifi-api/blob/main/LICENSE) +[](https://github.com/apache/nifi-api/actions/workflows/build.yml) + +The [Apache NiFi](https://nifi.apache.org) API repository contains public interfaces and classes for building extension components. + +## Requirements + +- Java 21 +- Maven 3.9 + +## Versioning + +The project follows [Semantic Versioning 2.0.0](https://semver.org). + +## Building + +This project uses [Maven](https://maven.apache.org) to build distribution packages. + +This project includes the [Maven Wrapper](https://maven.apache.org/wrapper/) to build with required Maven versions. + +``` +./mvnw install +``` + +## Verifying + +Code and documentation changes must pass build verification to be eligible for review. + +``` +./mvnw verify +``` + +The project uses several build plugins for static code analysis and license evaluation. + +- [Apache Maven Checkstyle Plugin](https://maven.apache.org/plugins/maven-checkstyle-plugin/) +- [Apache Maven PMD Plugin](https://maven.apache.org/plugins/maven-pmd-plugin/) +- [Apache Release Audit Tool Plugin](https://creadur.apache.org/rat/apache-rat-plugin/) +- [JaCoCo Maven Plugin](https://www.jacoco.org/jacoco/trunk/doc/maven.html) + +## Documentation + +The [Apache NiFi Documentation](https://nifi.apache.org/documentation/) includes reference information for project capabilities. + +## Contributing + +The [Apache NiFi Contributor Guide](https://cwiki.apache.org/confluence/display/NIFI/Contributor+Guide) +describes the process for getting involved in the development of this project. + +## Issues + +This project uses [Jira](https://issues.apache.org/jira/browse/NIFI) for tracking bugs and features. + +## Licensing + +This project is released under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..7c87426 --- /dev/null +++ b/checkstyle.xml
@@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<!-- SPDX-License-Identifier: Apache-2.0 --> +<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> +<module name="Checker"> + <module name="NewlineAtEndOfFile"/> + <module name="FileLength"/> + <module name="LineLength"> + <property name="max" value="200"/> + <property name="fileExtensions" value="java"/> + </module> + <module name="FileTabCharacter"/> + <module name="RegexpSingleline"> + <property name="format" value="\s+$"/> + <property name="minimum" value="0"/> + <property name="maximum" value="0"/> + <property name="message" value="Line has trailing spaces."/> + </module> + <module name="TreeWalker"> + <module name="MethodName"/> + <module name="HideUtilityClassConstructor"/> + <module name="InvalidJavadocPosition"/> + <module name="JavadocMethod"/> + <module name="JavadocType"/> + <module name="LocalFinalVariableName"/> + <module name="LocalVariableName"/> + <module name="MemberName"/> + <module name="PackageName"/> + <module name="ParameterName"/> + <module name="StaticVariableName"/> + <module name="TypeName"/> + <module name="AvoidStarImport"/> + <module name="IllegalImport"/> + <module name="RedundantImport"/> + <module name="UnusedImports"/> + <module name="MethodLength"> + <property name="max" value="250"/> + </module> + <module name="EmptyForIteratorPad"/> + <module name="GenericWhitespace"/> + <module name="MethodParamPad"/> + <module name="NoWhitespaceAfter"/> + <module name="NoWhitespaceBefore"/> + <module name="ParenPad"/> + <module name="TypecastParenPad"/> + <module name="WhitespaceAfter"/> + <module name="WhitespaceAround"/> + <module name="ModifierOrder"/> + <module name="AvoidNestedBlocks"/> + <module name="EmptyBlock"/> + <module name="LeftCurly"/> + <module name="NeedBraces"/> + <module name="RightCurly"/> + <module name="EmptyStatement"/> + <module name="EqualsHashCode"/> + <module name="IllegalInstantiation"/> + <module name="InnerAssignment"/> + <module name="MissingSwitchDefault"/> + <module name="MultipleVariableDeclarations"/> + <module name="SimplifyBooleanExpression"/> + <module name="SimplifyBooleanReturn"/> + <module name="InterfaceIsType"/> + <module name="ArrayTypeStyle"/> + <module name="TodoComment"/> + <module name="UpperEll"/> + <module name="UnusedLocalVariable"/> + </module> +</module>
diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/mvnw
@@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/mvnw.cmd
@@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c6eeb46 --- /dev/null +++ b/pom.xml
@@ -0,0 +1,281 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache</groupId> + <artifactId>apache</artifactId> + <version>33</version> + <relativePath/> + </parent> + + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-api</artifactId> + <version>2.0.0-SNAPSHOT</version> + <packaging>jar</packaging> + + <name>nifi-api</name> + <description>Apache NiFi API</description> + <url>https://nifi.apache.org</url> + <inceptionYear>2014</inceptionYear> + <organization> + <name>Apache NiFi Project</name> + <url>https://nifi.apache.org</url> + </organization> + <licenses> + <license> + <name>Apache License 2.0</name> + <url>https://www.apache.org/licenses/LICENSE-2.0</url> + <distribution>repo</distribution> + </license> + </licenses> + <developers> + <developer> + <name>Apache NiFi Developers</name> + <email>dev@nifi.apache.org</email> + <organization>Apache NiFi Project</organization> + <organizationUrl>https://nifi.apache.org</organizationUrl> + </developer> + </developers> + <mailingLists> + <mailingList> + <name>Developers</name> + <subscribe>dev-subscribe@nifi.apache.org</subscribe> + <unsubscribe>dev-unsubscribe@nifi.apache.org</unsubscribe> + <post>dev@nifi.apache.org</post> + <archive>https://lists.apache.org/list.html?dev@nifi.apache.org</archive> + </mailingList> + <mailingList> + <name>Users</name> + <subscribe>users-subscribe@nifi.apache.org</subscribe> + <unsubscribe>users-unsubscribe@nifi.apache.org</unsubscribe> + <post>users@nifi.apache.org</post> + <archive>https://lists.apache.org/list.html?users@nifi.apache.org</archive> + </mailingList> + <mailingList> + <name>Commits</name> + <subscribe>commits-subscribe@nifi.apache.org</subscribe> + <unsubscribe>commits-unsubscribe@nifi.apache.org</unsubscribe> + <post>commits@nifi.apache.org</post> + <archive>https://lists.apache.org/list.html?commits@nifi.apache.org</archive> + </mailingList> + </mailingLists> + <scm> + <connection>scm:git:https://github.com/apache/nifi-api</connection> + <developerConnection>scm:git:https://github.com/apache/nifi-api</developerConnection> + <url>https://github.com/apache/nifi-api</url> + </scm> + <issueManagement> + <system>jira</system> + <url>https://issues.apache.org/jira/browse/NIFI</url> + </issueManagement> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <maven.compiler.release>21</maven.compiler.release> + <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation> + <minimalJavaBuildVersion>21</minimalJavaBuildVersion> + <minimalMavenBuildVersion>3.9.9</minimalMavenBuildVersion> + + <checkstyle.version>10.18.1</checkstyle.version> + <junit-bom.version>5.11.0</junit-bom.version> + <jacoco.version>0.8.12</jacoco.version> + <maven-pmd-plugin.version>3.25.0</maven-pmd-plugin.version> + <mockito.version>5.13.0</mockito.version> + <pmd.version>7.5.0</pmd.version> + <swagger-annotations.version>2.2.23</swagger-annotations.version> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>${junit-bom.version}</version> + <scope>import</scope> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-bom</artifactId> + <version>${mockito.version}</version> + <scope>import</scope> + <type>pom</type> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <!-- Optional dependency for generating OpenAPI Specification --> + <dependency> + <groupId>io.swagger.core.v3</groupId> + <artifactId>swagger-annotations</artifactId> + <version>${swagger-annotations.version}</version> + <scope>compile</scope> + <optional>true</optional> + </dependency> + <!-- Test dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.version}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>${version.maven-checkstyle-plugin}</version> + <dependencies> + <dependency> + <groupId>com.puppycrawl.tools</groupId> + <artifactId>checkstyle</artifactId> + <version>${checkstyle.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <version>${maven-pmd-plugin.version}</version> + <dependencies> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-core</artifactId> + <version>${pmd.version}</version> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-java</artifactId> + <version>${pmd.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <configuration> + <useIdeaDefaultExcludes>true</useIdeaDefaultExcludes> + </configuration> + <executions> + <execution> + <goals> + <goal>check</goal> + </goals> + <phase>verify</phase> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <configuration> + <configLocation>checkstyle.xml</configLocation> + <includeTestSourceDirectory>true</includeTestSourceDirectory> + </configuration> + <executions> + <execution> + <id>check</id> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <skipIfEmpty>true</skipIfEmpty> + <archive> + <manifest> + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> + </manifest> + <manifestEntries> + <Automatic-Module-Name>${project.groupId}</Automatic-Module-Name> + </manifestEntries> + </archive> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <quiet>true</quiet> + <failOnError>true</failOnError> + <failOnWarnings>true</failOnWarnings> + <doclint>all,-missing</doclint> + </configuration> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <configuration> + <printFailingErrors>true</printFailingErrors> + <linkXRef>false</linkXRef> + <targetJdk>${maven.compiler.release}</targetJdk> + </configuration> + <executions> + <execution> + <id>check</id> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project>
diff --git a/src/main/java/org/apache/nifi/action/Action.java b/src/main/java/org/apache/nifi/action/Action.java new file mode 100644 index 0000000..44c28a6 --- /dev/null +++ b/src/main/java/org/apache/nifi/action/Action.java
@@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.action; + +import java.io.Serializable; +import java.util.Date; +import org.apache.nifi.action.component.details.ComponentDetails; +import org.apache.nifi.action.details.ActionDetails; + +/** + * An action taken on the flow by a user. + */ +public interface Action extends Serializable { + + Integer getId(); + + Date getTimestamp(); + + String getUserIdentity(); + + String getSourceId(); + + String getSourceName(); + + Component getSourceType(); + + ComponentDetails getComponentDetails(); + + Operation getOperation(); + + ActionDetails getActionDetails(); + +}
diff --git a/src/main/java/org/apache/nifi/action/Component.java b/src/main/java/org/apache/nifi/action/Component.java new file mode 100644 index 0000000..9a65185 --- /dev/null +++ b/src/main/java/org/apache/nifi/action/Component.java
@@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.action; + +/** + * Defines possible components for a given action. + */ +public enum Component { + + Controller, + Processor, + InputPort, + OutputPort, + ProcessGroup, + RemoteProcessGroup, + Funnel, + Connection, + ControllerService, + ReportingTask, + FlowAnalysisRule, + FlowRegistryClient, + ParameterContext, + ParameterProvider, + AccessPolicy, + User, + UserGroup, + Label; +}
diff --git a/src/main/java/org/apache/nifi/action/Operation.java b/src/main/java/org/apache/nifi/action/Operation.java new file mode 100644 index 0000000..1a199c5 --- /dev/null +++ b/src/main/java/org/apache/nifi/action/Operation.java
@@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.action; + +/** + * Defines possible operations for a given action. + */ +public enum Operation { + + Add("Add"), + Remove("Remove"), + Paste("Paste"), + Configure("Configure"), + Move("Move"), + Disconnect("Disconnect"), + Connect("Connect"), + Start("Start"), + Stop("Stop"), + Enable("Enable"), + Disable("Disable"), + Batch("Batch"), + Purge("Purge"), + ClearState("Clear State"), + StartVersionControl("Start Version Control"), + StopVersionControl("Stop Version Control"), + CommitLocalChanges("Commit Local Changes"), + RevertLocalChanges("Revert Local Changes"), + ChangeVersion("Change Version"), + RunOnce("RunOnce"); + + private final String label; + + Operation(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } +}
diff --git a/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java b/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java new file mode 100644 index 0000000..1eb35c1 --- /dev/null +++ b/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.action.component.details; + +import java.io.Serializable; + +/** + * Base interface for providing component details to an Action. + */ +public interface ComponentDetails extends Serializable { + +}
diff --git a/src/main/java/org/apache/nifi/action/details/ActionDetails.java b/src/main/java/org/apache/nifi/action/details/ActionDetails.java new file mode 100644 index 0000000..75ebd9d --- /dev/null +++ b/src/main/java/org/apache/nifi/action/details/ActionDetails.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.action.details; + +import java.io.Serializable; + +/** + * Provides additional details about a given action. + */ +public interface ActionDetails extends Serializable { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java b/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java new file mode 100644 index 0000000..2ef6aa8 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.time.Duration; + +public enum DefaultRunDuration { + NO_BATCHING(Duration.ZERO), + TWENTY_FIVE_MILLIS(Duration.ofMillis(25)), + FIFTY_MILLIS(Duration.ofMillis(50)), + ONE_HUNDRED_MILLIS(Duration.ofMillis(100)), + TWO_HUNDRED_FIFTY_MILLIS(Duration.ofMillis(250)), + FIVE_HUNDRED_MILLIS(Duration.ofMillis(500)), + ONE_SECOND(Duration.ofSeconds(1)), + TWO_SECONDS(Duration.ofSeconds(2)); + + private final Duration duration; + + DefaultRunDuration(final Duration duration) { + this.duration = duration; + } + + public Duration getDuration() { + return duration; + } +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.java new file mode 100644 index 0000000..9670535 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.java
@@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a component has more than one dynamic property + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DynamicProperties { + + DynamicProperty[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java new file mode 100644 index 0000000..5bccd48 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java
@@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.expression.ExpressionLanguageScope; + +/** + * An annotation that may be placed on a {@link ConfigurableComponent} to + * indicate that it supports a dynamic property. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DynamicProperty { + + String name(); + + String value(); + + String description(); + + ExpressionLanguageScope expressionLanguageScope() default ExpressionLanguageScope.NONE; + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java new file mode 100644 index 0000000..006f6ad --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java
@@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processor.Processor; +import org.apache.nifi.processor.Relationship; + +/** + * Annotation to indicate that a {@link Processor} supports dynamic + * relationship. A dynamic {@link Relationship} is one where the relationship is + * generated based on a user defined {@link PropertyDescriptor} + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DynamicRelationship { + + String name(); + + String description(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java b/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java new file mode 100644 index 0000000..372d80c --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java
@@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker interface that a Processor can use to indicate whether it accepts, requires, or forbids + * input from other Processors. This information is used by the framework in order to ensure that + * a Processor is marked as invalid if it is missing necessary input or has input that will be ignored. + * This information also is used by the NiFi UI in order to prevent users from making connections + * to Processors that don't make sense. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface InputRequirement { + Requirement value(); + + enum Requirement { + /** + * This value is used to indicate that the Processor requires input from other Processors + * in order to run. As a result, the Processor will not be valid if it does not have any + * incoming connections. + */ + INPUT_REQUIRED, + + /** + * This value is used to indicate that the Processor will consume data from an incoming + * connection but does not require an incoming connection in order to perform its task. + * If the {@link InputRequirement} annotation is not present, this is the default value + * that is used. + */ + INPUT_ALLOWED, + + /** + * This value is used to indicate that the Processor is a "Source Processor" and does + * not accept incoming connections. Because the Processor does not pull FlowFiles from + * an incoming connection, it can be very confusing for users who create incoming connections + * to the Processor. As a result, this value can be used in order to clarify that incoming + * connections will not be used. This prevents the user from even creating such a connection. + */ + INPUT_FORBIDDEN; + } +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java b/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java new file mode 100644 index 0000000..6443383 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a {@link org.apache.nifi.processor.Processor} implementation + * can use to indicate that the {@link org.apache.nifi.scheduling.ExecutionNode} + * of the processor is to be set to PRIMARY + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface PrimaryNodeOnly { +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java new file mode 100644 index 0000000..7094b92 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java
@@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.processor.Processor Processor} indicating that this + * processor reads a specific FlowFile attribute. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ReadsAttribute { + + String attribute(); + + String description() default ""; +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java new file mode 100644 index 0000000..fe1f8bd --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java
@@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.processor.Processor Processor} indicating that this + * processor reads specific FlowFile attributes. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ReadsAttributes { + + ReadsAttribute[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java b/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java new file mode 100644 index 0000000..0796199 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java
@@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a component can use to indicate that the framework should create a new ClassLoader + * for each instance of the component, copying all resources from the component's NARClassLoader to a + * new ClassLoader which will only be used by a given instance of the component. + * + * If cloneAncestorResources is set to true, the instance ClassLoader will include ancestor resources up to the + * first ClassLoader containing a controller service API referenced by the component, or up to the Jetty NAR. + * + * Example #1 - PutHDFS has this flag set to true and does not reference any controller services, so it will include + * resources from nifi-hadoop-nar, nifi-hadoop-libraries-nar, and nifi-standard-services-api-nar, stopping at nifi-jetty-nar. + * + * Example #2 - If PutHDFS referenced an SSLContext and has this flag set to true, then it would include + * resources from nifi-hadoop-nar, nifi-hadoop-libraries-nar, and stop before nifi-standard-services-api-nar. + * + * Example #3 - HBaseClientService_1_1_2 does not have this flag set so it defaults to false, and therefore includes + * only resources from the nifi-hbase-client-service-1_1_2-nar. + * + * NOTE: When this annotation is used it is important to note that each added instance of the component will increase + * the overall memory footprint more than that of a component without this annotation. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface RequiresInstanceClassLoading { + + boolean cloneAncestorResources() default false; + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java b/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java new file mode 100644 index 0000000..8411c6c --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java
@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marks the usage of a component as restricted to users with elevated privileges. + * </p> + * <p> + * A {@code Restricted} component is one that can be used to execute arbitrary unsanitized + * code provided by the operator through the NiFi REST API/UI or can be used to obtain + * or alter data on the NiFi host system using the NiFi OS credentials. These components + * could be used by an otherwise authorized NiFi user to go beyond the intended use of + * the application, escalate privilege, or could expose data about the internals of the + * NiFi process or the host system. All of these capabilities should be considered + * privileged, and admins should be aware of these capabilities and explicitly enable + * them for a subset of trusted users. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Restricted { + /** + * Provides a description of why the component usage is restricted. If using granular + * restrictions, specific explanations should be set in the Restriction. + * + * @return Description of restriction + */ + String value() default ""; + + /** + * Provides a listing of specific Restrictions. If unspecified, this component will + * require access to restricted components regardless of restrictions. + * + * @return Array of restrictions + */ + Restriction[] restrictions() default {}; + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java b/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java new file mode 100644 index 0000000..63efc98 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java
@@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.RequiredPermission; + +/** + * Specific restriction for a component. Indicates what the required permission is and why the restriction exists. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Restriction { + + /** + * Provides a listing of RequiredPermissions. + * + * @return Permission required for this restriction + */ + RequiredPermission requiredPermission(); + + /** + * Provides an explanation of why the component usage is restricted + * + * @return Explanation for this restriction + */ + String explanation(); + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java b/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java new file mode 100644 index 0000000..9a64f4b --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} + * implementation can use to indicate that its operations on FlowFiles can be + * safely repeated across process sessions. If a processor has this annotation + * and it allows the framework to manage session commit and rollback then the + * framework may elect to cascade a + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} given to this + * processor's onTrigger method to the onTrigger method of another processor. It + * can do this knowing that if something fails along a series of processors + * using this same session that it can all be safely rolled back without any ill + * effects on external services which could not be rolled back and thus all the + * processes could be safely repeated (implied idempotent behavior). + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SideEffectFree { +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java b/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java new file mode 100644 index 0000000..cfcc820 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java
@@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.components.state.StateManager; + +/** + * <p> + * Annotation that a Processor, ReportingTask, FlowAnalysisRule, ParameterProvider, or Controller Service can use to indicate + * that the component makes use of the {@link StateManager}. This annotation provides the + * user with a description of what information is being stored so that the user is able to + * understand what is shown to them and know what they are clearing should they choose to + * clear the state. Additionally, the UI will not show any state information to users if + * this annotation is not present. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Stateful { + /** + * Provides a description of what information is being stored in the {@link StateManager} + * + * @return Description of information stored + */ + String description(); + + /** + * Indicates the Scope(s) associated with the State that is stored and retrieved. + * + * @return Array of scopes for information stored + */ + Scope[] scopes(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java b/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java new file mode 100644 index 0000000..49136ad --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a Processor implementation can use to indicate that users + * should be able to supply a Batch Duration for the Processor. If a Processor + * uses this annotation, it is allowing the Framework to batch + * {@link org.apache.nifi.processor.ProcessSession ProcessSession}s' commits, as well as + * allowing the Framework to return the same ProcessSession multiple times from + * subsequent calls to + * {@link org.apache.nifi.processor.ProcessSessionFactory ProcessSessionFactory}. + * {@link org.apache.nifi.processor.ProcessSessionFactory#createSession() createSession()}. + * + * When this Annotation is used, it is important to note that calls to + * {@link org.apache.nifi.processor.ProcessSession#commit() ProcessSession.commit()} may + * not provide a guarantee that the data has been safely stored in NiFi's + * Content Repository or FlowFile Repository. Therefore, it is not appropriate, + * for instance, to use this annotation if the Processor will call + * ProcessSession.commit() to ensure data is persisted before deleting the data + * from a remote source. + * + * When the defaultDuration parameter is set, the processor is created with the supplied duration time, which can be adjusted afterwards. + * The supplied values can be selected from {@link org.apache.nifi.annotation.behavior.DefaultRunDuration}. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SupportsBatching { + DefaultRunDuration defaultDuration() default DefaultRunDuration.NO_BATCHING; +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java b/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java new file mode 100644 index 0000000..9927641 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Component annotation indicating support for dynamic properties that can be designated as sensitive for the purpose of + * persistence and framework processing + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SupportsSensitiveDynamicProperties { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java new file mode 100644 index 0000000..cea3c70 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +/** + * Represents a system resource. + */ +public enum SystemResource { + + CPU, DISK, MEMORY, NETWORK + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java new file mode 100644 index 0000000..66c8abb --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java
@@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.components.ConfigurableComponent Component} describes how this component may impact a + * system resource based on its configuration. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Repeatable(SystemResourceConsiderations.class) +public @interface SystemResourceConsideration { + + String DEFAULT_DESCRIPTION = "An instance of this component can cause high usage of this system resource. " + + "Multiple instances or high concurrency settings may result a degradation of performance."; + + /** + * The {@link SystemResource SystemResource} which may be affected by this component. + * + * @return System Resource to be considered + */ + SystemResource resource(); + + /** + * A description of how this component and its configuration may affect system resource usage. + * + * @return Description of resource consideration + */ + String description() default DEFAULT_DESCRIPTION; +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.java new file mode 100644 index 0000000..cd6c8c2 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.java
@@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.components.ConfigurableComponent Component} describes how this component may impact + * system resources based on its configuration. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SystemResourceConsiderations { + SystemResourceConsideration[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java new file mode 100644 index 0000000..974e358 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java
@@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} + * implementation can use to indicate that the Processor is not safe for + * concurrent execution of its onTrigger() method. By default, Processors are + * assumed to be safe for concurrent execution. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TriggerSerially { +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java new file mode 100644 index 0000000..db115cb --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} + * implementation can use to indicate that the Processor is to be triggered if + * any of its destinations has available space for incoming FlowFiles. By + * default, Processors are triggered only when all destinations report that they + * have available space (i.e., none of the outgoing Connections is full). + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TriggerWhenAnyDestinationAvailable { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java new file mode 100644 index 0000000..6fac03b --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java
@@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} + * implementation can use to indicate that the Processor should still be + * triggered even when it has no data in its work queue. + * </p> + * + * <p> + * A Processor is scheduled to be triggered based on its configured Scheduling Period + * and Scheduling Strategy. However, when the scheduling period elapses, the Processor + * will not be scheduled if it has no work to do. Normally, a Processor is said to have + * work to do if one of the following circumstances is true: + * </p> + * + * <ul> + * <li>An incoming Connection has data in its queue</li> + * <li>The Processor has no incoming Connections.</li> + * <li>All incoming Connections are self-loops (both the source and destination of the Connection are the same Processor). + * </ul> + * + * <p> + * If the Processor needs to be triggered to run even when the above conditions are all + * <code>false</code>, the Processor's class can be annotated with this annotation, which + * will cause the Processor to be triggered, even if its incoming queues are empty. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TriggerWhenEmpty { +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java new file mode 100644 index 0000000..d06af8f --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java
@@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.processor.Processor Processor} indicating that this + * processor writes/updates a specific FlowFile attribute. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface WritesAttribute { + + String attribute(); + + String description() default ""; +}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java new file mode 100644 index 0000000..603eb86 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java
@@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.behavior; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a + * {@link org.apache.nifi.processor.Processor Processor} indicating that this + * processor writes/updates specific FlowFile attributes. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface WritesAttributes { + + WritesAttribute[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java new file mode 100644 index 0000000..f1a145a --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.configuration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.scheduling.SchedulingStrategy; + +/** + * <p> + * Marker interface that a Processor can use to configure default settings for the schedule strategy, the period and the number of concurrent tasks. + * Marker interface that a ReportingTask can use to configure default settings the schedule strategy and the period. + * Note that the number of Concurrent tasks will be ignored if the annotation @TriggerSerialy is used + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DefaultSchedule { + + SchedulingStrategy strategy() default SchedulingStrategy.TIMER_DRIVEN; + String period() default "0 sec"; + int concurrentTasks() default 1; + +}
diff --git a/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java new file mode 100644 index 0000000..09402c7 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java
@@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.configuration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.logging.LogLevel; + +/** + * <p> + * Marker interface that a Processor can use to configure the yield duration, the penalty duration and the bulletin log level. + * Note that the number of Concurrent tasks will be ignored if the annotation @TriggerSerialy is used + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DefaultSettings { + String yieldDuration() default "1 sec"; + String penaltyDuration() default "30 sec"; + LogLevel bulletinLevel() default LogLevel.WARN; +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java b/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java new file mode 100644 index 0000000..5ac0e97 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java
@@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that may be placed on a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, + * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} allowing for a + * description to be provided. This description can be provided to a user in + * logs, UI, etc. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface CapabilityDescription { + + String value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java b/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java new file mode 100644 index 0000000..e385843 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java
@@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.ConfigurableComponent; + +/** + * Annotation that can be applied to a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} in order to + * warn about the deprecation of the component. The deprecation warning is informational only + * and doesn't affect the processor run time behavior in any way + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited + +public @interface DeprecationNotice { + Class<? extends ConfigurableComponent>[] alternatives() default {}; + + String[] classNames() default {}; + + String reason() default ""; +} +
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java new file mode 100644 index 0000000..562647e --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java
@@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * An annotation that can be used for Processors in order to explain a specific use case that can be + * accomplished using this Processor in conjunction with at least one other Processor. + * For Processors that are able to be used for multiple use cases, the component + * may be annotated with multiple MultiProcessorUseCase annotations. + * </p> + * <p> + * Note that this annotation differs from {@link UseCase} in that UseCase should describe a use case that is + * accomplished using only the extension that is annotated. In contrast, MultiProcessorUseCase documents a use case + * that is accomplished by using both the Processor that is annotated as well as other Processors. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Repeatable(MultiProcessorUseCases.class) +public @interface MultiProcessorUseCase { + + /** + * A simple 1 (at most 2) sentence description of the use case. This should not include any extraneous details, such + * as caveats, examples, etc. Those can be provided using the {@link #notes()} method. + * + * @return a simple description of the use case + */ + String description(); + + /** + * Most of the time, 1-2 sentences is sufficient to describe a use case. Those 1-2 sentence should then be returned + * by the {@link #description()}. In the event that the description is not sufficient, details may be provided to + * further explain, by providing examples, caveats, etc. + * + * @return any important notes that pertain to the use case + */ + String notes() default ""; + + /** + * An optional array of keywords that can be associated with the use case. + * @return keywords associated with the use case + */ + String[] keywords() default {}; + + /** + * An array of {@link ProcessorConfiguration}s that are necessary in order to accomplish the task described in this use case. + * @return an array of processor configurations + */ + ProcessorConfiguration[] configurations(); + +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java new file mode 100644 index 0000000..a7ae09b --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An enclosing annotation that can be used in order to use the {@link MultiProcessorUseCase} annotation in a repeated manner. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface MultiProcessorUseCases { + MultiProcessorUseCase[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java b/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java new file mode 100644 index 0000000..ec2ab1f --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java
@@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.documentation; + + +import org.apache.nifi.processor.Processor; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that can be used in conjunction with {@link MultiProcessorUseCase} in order to specify the different + * components that are involved in a given use case. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ProcessorConfiguration { + + /** + * Returns the class of the Processor that is to be used in the use case, if it is provided. Either the + * Processor Class or the Processor Class Name must be provided. + * + * @return the Processor's class, or <code>Processor</code> if the processor's classname is specified instead + */ + Class<? extends Processor> processorClass() default Processor.class; + + /** + * @return the fully qualified classname of the component + */ + String processorClassName() default ""; + + /** + * @return an explanation of how the Processor should be configured. + */ + String configuration(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java b/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java new file mode 100644 index 0000000..5eae159 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.components.ConfigurableComponent; + +/** + * Annotation that may be placed on a null {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, + * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} that indicates + * this component is related to the components listed. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SeeAlso { + + Class<? extends ConfigurableComponent>[] value() default {}; + + String[] classNames() default {}; +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/Tags.java b/src/main/java/org/apache/nifi/annotation/documentation/Tags.java new file mode 100644 index 0000000..a22cb28 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/Tags.java
@@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be applied to a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, + * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} in order to + * associate tags (keywords) with the component. These tags do not affect the + * component in any way but serve as additional documentation and can be used to + * sort/filter Processors. + * + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Tags { + + String[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java b/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java new file mode 100644 index 0000000..478ba1e --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java
@@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.documentation; + +import org.apache.nifi.annotation.behavior.InputRequirement; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * An annotation that can be used for extension points in order to explain a specific use case that can be + * accomplished using this extension. For components that are able to be used for multiple use cases, the component + * may be annotated with multiple UseCase annotations. + * </p> + * <p> + * Note that this annotation differs from {@link CapabilityDescription} in that CapabilityDescription should describe the + * general purpose of the extension point. UseCase, on the other hand, documents one very specific use case that + * can be accomplished. Some extension points may use only a single UseCase annotation while others may accomplish + * many use cases. + * </p> + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Repeatable(UseCases.class) +public @interface UseCase { + + /** + * A simple 1 (at most 2) sentence description of the use case. This should not include any extraneous details, such + * as caveats, examples, etc. Those can be provided using the {@link #notes()} method. + * + * @return a simple description of the use case + */ + String description(); + + /** + * Most of the time, 1-2 sentences is sufficient to describe a use case. Those 1-2 sentence should then be returned + * by the {@link #description()}. In the event that the description is not sufficient, details may be provided to + * further explain, by providing examples, caveats, etc. + * + * @return any important notes that pertain to the use case + */ + String notes() default ""; + + /** + * Most Processors specify an InputRequirement of either {@link InputRequirement.Requirement#INPUT_REQUIRED INPUT_REQUIRED} + * or {@link InputRequirement.Requirement#INPUT_FORBIDDEN}. However, some Processors use {@link InputRequirement.Requirement#INPUT_ALLOWED} + * because some use cases require input while others do not. The inputRequirement here is only relevant for Processors that use + * an InputRequirement of {@link InputRequirement.Requirement#INPUT_ALLOWED} and can indicate whether or not the Processor should have + * input (aka incoming Connections) for this particular use case. + * + * @return the {@link InputRequirement} that corresponds to this use case. + */ + InputRequirement.Requirement inputRequirement() default InputRequirement.Requirement.INPUT_ALLOWED; + + /** + * An optional array of keywords that can be associated with the use case. + * @return keywords associated with the use case + */ + String[] keywords() default {}; + + /** + * A description of how to configure the extension for this particular use case. + * @return a description of how to configure the extension for this particular use case. + */ + String configuration() default ""; +}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java b/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java new file mode 100644 index 0000000..f64c8e5 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An enclosing annotation that can be used in order to use the {@link UseCase} annotation in a repeated manner. + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface UseCases { + UseCase[] value(); +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java new file mode 100644 index 0000000..4933a38 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java
@@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.registry.flow.FlowRegistryClient FlowRegistryClient}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, + * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation + * can use to indicate a method should be called whenever the component is added + * to the flow. This method will be called once for the entire life of a + * component instance. + * </p> + * + * <p> + * Methods with this annotation are called without any arguments, as all + * settings and properties can be assumed to be the defaults. + * </p> + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnAdded { +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java new file mode 100644 index 0000000..72c5bd3 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java
@@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.annotation.lifecycle; + +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.processor.ProcessContext; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker Annotation that a Processor, Reporting Task, or Controller Service can use to indicate + * that the method with this Annotation should be invoked whenever the component's configuration + * is restored after a restart of NiFi. + * </p> + * + * <p> + * Methods with this annotation are permitted to take no arguments or to take a + * single argument. If using a single argument, that argument must be of type + * {@link ConfigurationContext} if the component is a ReportingTask or a + * ControllerService. If the component is a Processor, then the argument must be + * of type {@link ProcessContext}. + * </p> + * + * <p> + * Whenever a new component is added to the flow, this method will be called immediately, since + * there is no configuration to restore (in this case all configuration has already been restored, + * since there is no configuration to restore). + * </p> + * + * @since 0.5.0 + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnConfigurationRestored { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java new file mode 100644 index 0000000..c8d77e3 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java
@@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.controller.ConfigurationContext; + +/** + * <p> + * Marker annotation a + * {@link org.apache.nifi.controller.ControllerService ControllerService} can + * use to indicate a method should be called whenever the service is disabled. + * </p> + * + * <p> + * Methods using this annotation are permitted to take zero arguments or to take + * a single argument of type {@link ConfigurationContext}. If a method with this + * annotation throws a Throwable, a log message and bulletin will be issued for + * the service, but the service will still be marked as Disabled. The failing + * method will not be called again until the service is enabled and disabled again. + * This is done in order to prevent a ControllerService from continually failing + * in such a way that the service could not be disabled and updated without + * restarting the instance of NiFi. + * </p> + * + * <p> + * Note that this annotation will be ignored if applied to a ReportingTask, ParameterProvider or + * Processor. For a Controller Service, enabling and disabling are considered + * lifecycle events, as the action makes them usable or unusable by other + * components. However, for a Processor and a Reporting Task, these are not + * lifecycle events but rather a mechanism to allow a component to be excluded + * when starting or stopping a group of components. + * </p> + * + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnDisabled { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java new file mode 100644 index 0000000..28132c8 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation a + * {@link org.apache.nifi.controller.ControllerService ControllerService} can + * use to indicate a method should be called whenever the service is enabled. + * Any method that has this annotation will be called every time a user enables + * the service. Additionally, each time that NiFi is restarted, if NiFi is + * configured to "auto-resume state" and the service is enabled, the method will + * be invoked. + * </p> + * + * <p> + * Methods using this annotation must take either 0 arguments or a single + * argument of type + * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}. + * </p> + * + * <p> + * If a method with this annotation throws a Throwable, a log message and + * bulletin will be issued for the component. In this event, the service will + * remain in an 'ENABLING' state and will not be usable. All methods with this + * annotation will then be called again after a delay. The service will not be + * made available for use until all methods with this annotation have returned + * without throwing anything. + * </p> + * + * <p> + * Note that this annotation will be ignored if applied to a ReportingTask, ParameterProvider or + * Processor. For a Controller Service, enabling and disabling are considered + * lifecycle events, as the action makes them usable or unusable by other + * components. However, for a Processor and a Reporting Task, these are not + * lifecycle events but rather a mechanism to allow a component to be excluded + * when starting or stopping a group of components. + * </p> + * + * + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnEnabled { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java new file mode 100644 index 0000000..172979e --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java
@@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.processor.ProcessContext; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.registry.flow.FlowRegistryClient FlowRegistryClient}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, + * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation + * can use to indicate a method should be called whenever the component is + * removed from the flow. This method will be called once for the entire life of + * a component instance. If the method throw any Throwable, that Throwable will + * be caught and logged but will not prevent subsequent methods with this + * annotation or removal of the component from the flow. + * </p> + * + * <p> + * Methods with this annotation are permitted to take no arguments or to take a + * single argument. If using a single argument, that argument must be of type + * {@link ConfigurationContext} if the component is a ReportingTask, a ParameterProvider, or a + * ControllerService. If the component is a Processor, then the argument must be + * of type {@link ProcessContext}. + * </p> + * + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnRemoved { +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java new file mode 100644 index 0000000..b320858 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java
@@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation + * can use to indicate a method should be called whenever the component is + * scheduled to run. This will be called before any call to 'onTrigger' and will + * be called once each time a Processor or Reporting Task is scheduled to run. + * This occurs in one of two ways: either a user clicks to schedule the + * component to run, or NiFi is restarted with the "auto-resume state" + * configuration set to true (the default value) and the component is already + * running. + * </p> + * + * <p> + * Methods using this annotation must take either 0 arguments or a single + * argument. + * </p> + * + * <p> + * If using 1 argument and the component using the annotation is a Processor, + * that argument must be of type + * {@link org.apache.nifi.processor.ProcessContext ProcessContext}. + * </p> + * + * <p> + * If using 1 argument and the component using the annotation is a Reporting + * Task, that argument must be of type + * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}. + * </p> + * + * <p> + * If any method annotated with this annotation throws any Throwable, the + * framework will wait a while and then attempt to invoke the method again. This + * will continue until the method succeeds, and the component will then be + * scheduled to run after this method return successfully. + * </p> + * + * <p><b>Implementation Guidelines:</b> + * <ul> + * <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is + * expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li> + * <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period + * of time (the duration is configurable in the properties file), the Thread may be interrupted.</li> + * <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow + * {@link InterruptedException}.</li> + * </ul> + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnScheduled { +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java new file mode 100644 index 0000000..88a3ec3 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.processor.ProcessContext; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, + * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation + * can use to indicate a method should be called whenever the flow is being + * shutdown. This will be called at most once for each component in a JVM + * lifetime. It is not, however, guaranteed that this method will be called on + * shutdown, as the service may be killed suddenly. + * </p> + * + * <p> + * Methods with this annotation are permitted to take either 0 or 1 argument. If + * an argument is used, it must be of type {@link ConfigurationContext} if the + * component is a ReportingTask or Controller Service, or of type + * {@link ProcessContext} if the component is a Processor. + * </p> + * + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnShutdown { +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java new file mode 100644 index 0000000..647c2ec --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java
@@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.processor.ProcessContext; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation + * can use to indicate that a method should be called whenever the component is + * no longer scheduled to run. Methods marked with this annotation will be + * invoked each time the component is stopped and will be invoked only after the + * last thread has returned from the <code>onTrigger</code> method. + * </p> + * + * <p> + * This means that the thread executing in this method will be the only thread + * executing in any part of the Processor. However, since other threads may + * later execute other parts of the code, member variables must still be + * protected appropriately. However, access to multiple variables need not be + * atomic. + * </p> + * + * <p> + * To indicate that a method should be called immediately when a component is no + * longer scheduled to run (as opposed to after all threads have returned from + * the <code>onTrigger</code> method), see the {@link OnUnscheduled} annotation. + * </p> + * + * <p> + * Methods with this annotation are permitted to take either 0 or 1 argument. If + * an argument is used, it must be of type {@link ConfigurationContext} if the + * component is a ReportingTask or of type {@link ProcessContext} if the + * component is a Processor. + * </p> + * + * <p><b>Implementation Guidelines:</b> + * <ul> + * <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is + * expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li> + * <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period + * of time (the duration is configurable in the properties file), the Thread may be interrupted.</li> + * <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow + * {@link InterruptedException}.</li> + * </ul> + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnStopped { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java new file mode 100644 index 0000000..ea041ae --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} should be + * called whenever the component is no longer scheduled to run. Methods marked + * with this annotation will be invoked each time the framework is notified to + * stop scheduling the component. This method is invoked as other threads are + * potentially running. To invoke a method after all threads have finished + * processing, see the {@link OnStopped} annotation. + * </p> + * + * <p> + * Methods using this annotation must take either 0 arguments or a single + * argument. + * </p> + * + * <p> + * If using 1 argument and the component using the annotation is a Processor, + * that argument must be of type + * {@link org.apache.nifi.processor.ProcessContext ProcessContext}. + * </p> + * + * <p> + * If using 1 argument and the component using the annotation is a Reporting + * Task, that argument must be of type + * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}. + * </p> + * + * <p><b>Implementation Guidelines:</b> + * <ul> + * <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is + * expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li> + * <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period + * of time (the duration is configurable in the properties file), the Thread may be interrupted.</li> + * <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow + * {@link InterruptedException}.</li> + * </ul> + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnUnscheduled { +}
diff --git a/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java b/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java new file mode 100644 index 0000000..e3ff401 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java
@@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.annotation.notification; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p> + * Marker annotation that a component can use to indicate that a method should be + * called whenever the state of the Primary Node in a cluster has changed. + * </p> + * + * <p> + * Methods with this annotation should take either no arguments or one argument of type + * {@link PrimaryNodeState}. The {@link PrimaryNodeState} provides context about what changed + * so that the component can take appropriate action. + * </p> + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnPrimaryNodeStateChange { + +}
diff --git a/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java b/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java new file mode 100644 index 0000000..539e5f0 --- /dev/null +++ b/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java
@@ -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. + */ +package org.apache.nifi.annotation.notification; + +/** + * Represents a state change that occurred for the Primary Node of a NiFi cluster. + */ +public enum PrimaryNodeState { + /** + * The node receiving this state has been elected the Primary Node of the NiFi cluster. + */ + ELECTED_PRIMARY_NODE, + + /** + * The node receiving this state was the Primary Node but has now had its Primary Node + * role revoked. + */ + PRIMARY_NODE_REVOKED; +}
diff --git a/src/main/java/org/apache/nifi/asset/Asset.java b/src/main/java/org/apache/nifi/asset/Asset.java new file mode 100644 index 0000000..3e8c026 --- /dev/null +++ b/src/main/java/org/apache/nifi/asset/Asset.java
@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.asset; + +import java.io.File; +import java.util.Optional; + +/** + * An Asset is a representation of some resource that is necessary in order to run a dataflow. + * An Asset is always accessed as a local file. + */ +public interface Asset { + + /** + * Returns a unique identifier for the Asset + * + * @return Asset Identifier + */ + String getIdentifier(); + + /** + * Returns the identifier of the parameter context the Asset belongs to + * + * @return Parameter Context Identifier + */ + String getParameterContextIdentifier(); + + /** + * Returns the name of the Asset + * + * @return Asset Name + */ + String getName(); + + /** + * Returns the local file that the Asset is associated with + * + * @return Asset File + */ + File getFile(); + + /** + * Returns the digest of the contents of the local file that the Asset is associated with. + * The digest will not be present when the asset is considered missing and the local file does not exist. + * + * @return Asset Digest or empty when not found + */ + Optional<String> getDigest(); +}
diff --git a/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java b/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java new file mode 100644 index 0000000..2b0a061 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java
@@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public abstract class AbstractConfigurableComponent implements ConfigurableComponent { + + /** + * Allows subclasses to perform their own validation on the already set + * properties. Since each property is validated as it is set this allows + * validation of groups of properties together. Default return is an empty + * set. + * + * This method will be called only when it has been determined that all + * property values are valid according to their corresponding + * PropertyDescriptor's validators. + * + * @param validationContext provides a mechanism for obtaining externally + * managed values, such as property values and supplies convenience methods + * for operating on those values + * + * @return Collection of ValidationResult objects that will be added to any + * other validation findings - may be null + */ + protected Collection<ValidationResult> customValidate(final ValidationContext validationContext) { + return Collections.emptySet(); + } + + /** + * @param descriptorName to lookup the descriptor + * @return a PropertyDescriptor for the name specified that is fully + * populated + */ + @Override + public final PropertyDescriptor getPropertyDescriptor(final String descriptorName) { + final PropertyDescriptor specDescriptor = new PropertyDescriptor.Builder().name(descriptorName).build(); + return getPropertyDescriptor(specDescriptor); + } + + private PropertyDescriptor getPropertyDescriptor(final PropertyDescriptor specDescriptor) { + //check if property supported + PropertyDescriptor descriptor = getSupportedPropertyDescriptor(specDescriptor); + if (descriptor != null) { + return descriptor; + } + + descriptor = getSupportedDynamicPropertyDescriptor(specDescriptor.getName()); + if (descriptor != null && !descriptor.isDynamic()) { + descriptor = new PropertyDescriptor.Builder().fromPropertyDescriptor(descriptor).dynamic(true).build(); + } + + if (descriptor == null) { + descriptor = new PropertyDescriptor.Builder().fromPropertyDescriptor(specDescriptor).addValidator(Validator.INVALID).dynamic(true).build(); + } + return descriptor; + } + + private PropertyDescriptor getSupportedPropertyDescriptor(final PropertyDescriptor specDescriptor) { + final List<PropertyDescriptor> supportedDescriptors = getSupportedPropertyDescriptors(); + if (supportedDescriptors != null) { + for (final PropertyDescriptor desc : supportedDescriptors) { //find actual descriptor + if (specDescriptor.equals(desc)) { + return desc; + } + } + } + + return null; + } + + @Override + public final Collection<ValidationResult> validate(final ValidationContext context) { + // goes through context properties, should match supported properties + supported dynamic properties + final Collection<ValidationResult> results = new ArrayList<>(); + final Set<PropertyDescriptor> contextDescriptors = context.getProperties().keySet(); + + for (final PropertyDescriptor descriptor : contextDescriptors) { + // If the property descriptor's dependency is not satisfied, the property does not need to be considered, as it's not relevant to the + // component's functionality. + final boolean dependencySatisfied = context.isDependencySatisfied(descriptor, this::getPropertyDescriptor); + if (!dependencySatisfied) { + continue; + } + + validateDependencies(descriptor, context, results); + + String value = context.getProperty(descriptor).getValue(); + if (value == null) { + value = descriptor.getDefaultValue(); + } + + if (value == null && descriptor.isRequired()) { + String displayName = descriptor.getDisplayName(); + ValidationResult.Builder builder = new ValidationResult.Builder().valid(false).input(null).subject(displayName != null ? displayName : descriptor.getName()); + builder = (displayName != null) ? builder.explanation(displayName + " is required") : builder.explanation(descriptor.getName() + " is required"); + results.add(builder.build()); + continue; + } else if (value == null) { + continue; + } + + final ValidationResult result = descriptor.validate(value, context); + if (!result.isValid()) { + results.add(result); + } + } + + // only run customValidate if regular validation is successful. This allows Processor developers to not have to check + // if values are null or invalid so that they can focus only on the interaction between the properties, etc. + if (results.isEmpty()) { + final Collection<ValidationResult> customResults = customValidate(context); + if (null != customResults) { + for (final ValidationResult result : customResults) { + if (!result.isValid()) { + results.add(result); + } + } + } + } + + return results; + } + + private void validateDependencies(final PropertyDescriptor descriptor, final ValidationContext context, final Collection<ValidationResult> results) { + // Ensure that we don't have any dependencies on non-existent properties. + final Set<PropertyDependency> dependencies = descriptor.getDependencies(); + for (final PropertyDependency dependency : dependencies) { + final String dependentPropertyName = dependency.getPropertyName(); + + // If there's a supported property descriptor then all is okay. + final PropertyDescriptor specDescriptor = new PropertyDescriptor.Builder().name(dependentPropertyName).build(); + final PropertyDescriptor supportedDescriptor = getSupportedPropertyDescriptor(specDescriptor); + if (supportedDescriptor != null) { + continue; + } + + final PropertyDescriptor dynamicPropertyDescriptor = getSupportedDynamicPropertyDescriptor(dependentPropertyName); + if (dynamicPropertyDescriptor == null) { + results.add(new ValidationResult.Builder() + .subject(descriptor.getDisplayName()) + .valid(false) + .explanation("Property depends on property " + dependentPropertyName + ", which is not a known property") + .build()); + } + + // Dependent property is supported as a dynamic property. This is okay as long as there is a value set. + final PropertyValue value = context.getProperty(dynamicPropertyDescriptor); + if (value == null || !value.isSet()) { + results.add(new ValidationResult.Builder() + .subject(descriptor.getDisplayName()) + .valid(false) + .explanation("Property depends on property " + dependentPropertyName + ", which is not a known property") + .build()); + } + } + + } + + /** + * Hook method allowing subclasses to eagerly react to a configuration + * change for the given property descriptor. As an alternative to using this + * method a processor may simply get the latest value whenever it needs it + * and if necessary lazily evaluate it. + * + * @param descriptor of the modified property + * @param oldValue non-null property value (previous) + * @param newValue the new property value or if null indicates the property + * was removed + */ + @Override + public void onPropertyModified(final PropertyDescriptor descriptor, final String oldValue, final String newValue) { + } + + /** + * <p> + * Used to allow subclasses to determine what PropertyDescriptor if any to + * use when a property is requested for which a descriptor is not already + * registered. By default this method simply returns a null descriptor. By + * overriding this method processor implementations can support dynamic + * properties since this allows them to register properties on demand. It is + * acceptable for a dynamically generated property to indicate it is + * required so long as it is understood it is only required once set. + * Dynamic properties by definition cannot be required until used.</p> + * + * <p> + * This method should be side effect free in the subclasses in terms of how + * often it is called for a given property name because there is guarantees + * how often it will be called for a given property name.</p> + * + * <p> + * Default is null. + * + * @param propertyDescriptorName used to lookup if any property descriptors exist for that name + * @return new property descriptor if supported + */ + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) { + return null; + } + + /** + * Allows subclasses to register which property descriptor objects are + * supported. Default return is an empty set. + * + * @return PropertyDescriptor objects this processor currently supports + */ + protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { + return Collections.emptyList(); + } + + @Override + public final List<PropertyDescriptor> getPropertyDescriptors() { + final List<PropertyDescriptor> supported = getSupportedPropertyDescriptors(); + return supported == null ? Collections.emptyList() : Collections.unmodifiableList(supported); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ConfigurableComponent)) { + return false; + } + + final ConfigurableComponent other = (ConfigurableComponent) obj; + return getIdentifier().equals(other.getIdentifier()); + } + + @Override + public int hashCode() { + return 235 + getIdentifier().hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[id=" + getIdentifier() + "]"; + } + +}
diff --git a/src/main/java/org/apache/nifi/components/AllowableValue.java b/src/main/java/org/apache/nifi/components/AllowableValue.java new file mode 100644 index 0000000..9cae438 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/AllowableValue.java
@@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.util.Objects; + +/** + * <p> + * Represents a valid value for a {@link PropertyDescriptor} + * </p> + */ +public class AllowableValue implements DescribedValue { + + private final String value; + private final String displayName; + private final String description; + + /** + * Constructs a new AllowableValue with the given value and and the same + * display name and no description. + * + * @param value that is allowed + */ + public AllowableValue(final String value) { + this(value, value); + } + + /** + * Constructs a new AllowableValue with the given value and display name and + * no description + * + * @param value that is allowed + * @param displayName to display for the value + * @throws NullPointerException if either argument is null + */ + public AllowableValue(final String value, final String displayName) { + this(value, displayName, null); + } + + /** + * Constructs a new AllowableValue with the given value, display name, and + * description + * + * @param value that is valid + * @param displayName to show for the value + * @param description of the value + * @throws NullPointerException if identifier or value is null + */ + public AllowableValue(final String value, final String displayName, final String description) { + this.value = Objects.requireNonNull(value); + this.displayName = Objects.requireNonNull(displayName); + this.description = description; + } + + public static AllowableValue fromDescribedValue(final DescribedValue describedValue) { + if (describedValue instanceof AllowableValue allowableValue) { + return allowableValue; + } + + return new AllowableValue(describedValue.getValue(), describedValue.getDisplayName(), describedValue.getDescription()); + } + + /** + * @return the value of this AllowableValue + */ + @Override + public String getValue() { + return value; + } + + /** + * @return a human-readable name for this AllowableValue + */ + @Override + public String getDisplayName() { + return displayName; + } + + /** + * @return a description for this value, or <code>null</code> if no + * description was provided + */ + @Override + public String getDescription() { + return description; + } + + /** + * @return true if <code>this</code> is equal to <code>obj</code> of <code>obj</code> is the + * same object as <code>this</code> or if <code>obj</code> is an instance of + * <code>AllowableValue</code> and both have the same value, or if + * <code>obj</code> is a String and is equal to + * {@link #getValue() this.getValue()}. + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof AllowableValue other) { + return this.value.equals(other.getValue()); + } else if (obj instanceof String) { + return this.value.equals(obj); + } + + return false; + } + + /** + * @return based solely off of the value + */ + @Override + public int hashCode() { + return 23984731 + 17 * value.hashCode(); + } + + @Override + public String toString() { + return value; + } +}
diff --git a/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java b/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java new file mode 100644 index 0000000..9d36193 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java
@@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components; + +import org.apache.nifi.context.PropertyContext; + +/** + * <p> + * There are times when a component must be created in such a way that each instance gets its own ClassLoader hierarchy, + * rather than sharing the ClassLoader with other components (see {@link org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading @RequiresInstanceClassLoading}). + * This, however, can be extremely expensive, as all of the classes must be loaded again for each instance of the component. When thousands of these + * components are used in a single flow, the startup time can be great, and it can lead to massive amounts of RAM being required. + * </p> + * + * <p> + * For components that do require instance ClassLoading that clones ancestor resources, this interface can be optional implemented by the component. + * If the interface is implemented, the component is given the opportunity to return a distinct "key" that can be used to identify instances that may share + * the same ClassLoader. + * </p> + */ +public interface ClassloaderIsolationKeyProvider { + + /** + * <p> + * Determines the key that identifies a shared ClassLoader that this component may use. Any two instances of the same component that return + * the same key may be assigned the same base ClassLoader (though it is not guaranteed that this will be the case). + * </p> + * + * <p> + * If a subsequent call to this method returns a different value, the component will be recreated with a different ClassLoader. + * </p> + * + * <p> + * Implementation Note: for components that implement this interface, this method will be called often. Therefore, performance characteristics + * of the implementation are critical. The method is expected to return the value of a configured property or derive a value to return based off + * of the values of a few properties. Accessing a remote resource, is too expensive. If the necessary computation is non-trivial, then it should be + * performed out of band and the pre-computed value simply returned by this method. + * </p> + * + * @param context the PropertyContext that can be used for determining the key + * @return a distinct key that can be used to indicate which shared ClassLoader is allowed to be used + */ + String getClassloaderIsolationKey(PropertyContext context); + +}
diff --git a/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java b/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java new file mode 100644 index 0000000..9f2ab77 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java
@@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components; + +public class ConfigVerificationResult { + private final Outcome outcome; + private final String verificationStepName; + private final String explanation; + + private ConfigVerificationResult(final Builder builder) { + outcome = builder.outcome; + verificationStepName = builder.verificationStepName; + explanation = builder.explanation; + } + + public Outcome getOutcome() { + return outcome; + } + + public String getVerificationStepName() { + return verificationStepName; + } + + public String getExplanation() { + return explanation; + } + + @Override + public String toString() { + return "ConfigVerificationResult[" + + "outcome=" + outcome + + ", verificationStepName='" + verificationStepName + "'" + + ", explanation='" + explanation + "']"; + } + + public static class Builder { + private Outcome outcome = Outcome.SKIPPED; + private String verificationStepName = "Unknown Step Name"; + private String explanation; + + public Builder outcome(final Outcome outcome) { + this.outcome = outcome; + return this; + } + + public Builder verificationStepName(final String verificationStepName) { + this.verificationStepName = verificationStepName; + return this; + } + + public Builder explanation(final String explanation) { + this.explanation = explanation; + return this; + } + + public ConfigVerificationResult build() { + return new ConfigVerificationResult(this); + } + } + + public enum Outcome { + SUCCESSFUL, + + FAILED, + + SKIPPED; + } +}
diff --git a/src/main/java/org/apache/nifi/components/ConfigurableComponent.java b/src/main/java/org/apache/nifi/components/ConfigurableComponent.java new file mode 100644 index 0000000..4c5537e --- /dev/null +++ b/src/main/java/org/apache/nifi/components/ConfigurableComponent.java
@@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.util.Collection; +import java.util.List; +import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored; + +public interface ConfigurableComponent { + + /** + * Validates a set of properties, returning ValidationResults for any + * invalid properties. All defined properties will be validated. If they are + * not included in the purposed configuration, the default value will + * be used. + * + * @param context of validation + * @return Collection of validation result objects for any invalid findings + * only. If the collection is empty then the component is valid. Guaranteed + * non-null + */ + Collection<ValidationResult> validate(ValidationContext context); + + /** + * @param name to lookup the descriptor + * @return the PropertyDescriptor with the given name, if it exists; + * otherwise, returns <code>null</code> + */ + PropertyDescriptor getPropertyDescriptor(String name); + + /** + * Hook method allowing subclasses to eagerly react to a configuration + * change for the given property descriptor. This method will be invoked + * regardless of property validity. As an alternative to using this method, + * a component may simply get the latest value whenever it needs it and if + * necessary lazily evaluate it. Any throwable that escapes this method will + * simply be ignored. + * + * When NiFi is restarted, this method will be called for each 'dynamic' property that is + * added, as well as for each property that is not set to the default value. I.e., if the + * Properties are modified from the default values. If it is undesirable for your use case + * to react to properties being modified in this situation, you can add the {@link OnConfigurationRestored} + * annotation to a method - this will allow the Processor to know when configuration has + * been restored, so that it can determine whether or not to perform some action in the + * onPropertyModified method. + * + * @param descriptor the descriptor for the property being modified + * @param oldValue the value that was previously set, or null if no value + * was previously set for this property + * @param newValue the new property value or if null indicates the property + * was removed + */ + void onPropertyModified(PropertyDescriptor descriptor, String oldValue, String newValue); + + /** + * Returns a {@link List} of all {@link PropertyDescriptor}s that this + * component supports. + * + * @return PropertyDescriptor objects this component currently supports + */ + List<PropertyDescriptor> getPropertyDescriptors(); + + /** + * @return the unique identifier that the framework assigned to this + * component + */ + String getIdentifier(); + +}
diff --git a/src/main/java/org/apache/nifi/components/DescribedValue.java b/src/main/java/org/apache/nifi/components/DescribedValue.java new file mode 100644 index 0000000..87ffc2f --- /dev/null +++ b/src/main/java/org/apache/nifi/components/DescribedValue.java
@@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +/** + * Describes a component property value with display name and description. + */ +public interface DescribedValue { + + /** + * @return the property value as a string + */ + String getValue(); + + /** + * @return the property display name as a string + */ + String getDisplayName(); + + /** + * @return the proeprty description as a string + */ + String getDescription(); +}
diff --git a/src/main/java/org/apache/nifi/components/PortFunction.java b/src/main/java/org/apache/nifi/components/PortFunction.java new file mode 100644 index 0000000..a746eb3 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/PortFunction.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components; + +public enum PortFunction { + /** + * Port functions as a standard port, transferring FlowFiles to all outgoing connections. + */ + STANDARD, + + /** + * Port denotes that the invocation of the stateless flow has failed. If run using the Standard Engine, will operate as a Standard port. If running + * using the Stateless Engine, the transaction is rolled back, and any input FlowFiles will be transferred to this Port. + */ + FAILURE; +}
diff --git a/src/main/java/org/apache/nifi/components/PropertyDependency.java b/src/main/java/org/apache/nifi/components/PropertyDependency.java new file mode 100644 index 0000000..275248e --- /dev/null +++ b/src/main/java/org/apache/nifi/components/PropertyDependency.java
@@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class PropertyDependency { + private final String propertyName; + private final String displayName; + private final Set<String> dependentValues; + + /** + * Creates a dependency that is satisfied if any value is set for the property with the given name + * @param propertyName the name of the property that is depended upon + * @param propertyDisplayName the display name of the property that is depended upon + */ + public PropertyDependency(final String propertyName, final String propertyDisplayName) { + this.propertyName = Objects.requireNonNull(propertyName); + this.displayName = propertyDisplayName == null ? propertyName : propertyDisplayName; + this.dependentValues = null; + } + + /** + * Creates a dependency that is satisfied only if the property with the given name has a value that is in the given set of dependent values + * @param propertyName the name of the property that is depended upon + * @param propertyDisplayName the display name of the property that is depended upon + * @param dependentValues the values that satisfy the dependency + */ + public PropertyDependency(final String propertyName, final String propertyDisplayName, final Set<String> dependentValues) { + this.propertyName = Objects.requireNonNull(propertyName); + this.displayName = propertyDisplayName == null ? propertyName : propertyDisplayName; + this.dependentValues = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(dependentValues))); + } + + /** + * @return the name of the property that is depended upon + */ + public String getPropertyName() { + return propertyName; + } + + /** + * @return the display name of the property that is depended upon + */ + public String getPropertyDisplayName() { + return displayName; + } + + /** + * @return the Set of values that satisfy the dependency + */ + public Set<String> getDependentValues() { + return dependentValues; + } + + @Override + public String toString() { + return "PropertyDependency[propertyName=" + propertyName + ", displayName=" + displayName + ", dependentValues=" + dependentValues + "]"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final PropertyDependency that = (PropertyDependency) o; + return Objects.equals(getPropertyName(), that.getPropertyName()) + && Objects.equals(getDependentValues(), that.getDependentValues()); + } + + @Override + public int hashCode() { + return Objects.hash(getPropertyName(), getDependentValues()); + } +}
diff --git a/src/main/java/org/apache/nifi/components/PropertyDescriptor.java b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java new file mode 100644 index 0000000..8f61545 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
@@ -0,0 +1,911 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import org.apache.nifi.components.resource.ResourceCardinality; +import org.apache.nifi.components.resource.ResourceDefinition; +import org.apache.nifi.components.resource.ResourceReference; +import org.apache.nifi.components.resource.ResourceType; +import org.apache.nifi.components.resource.StandardResourceDefinition; +import org.apache.nifi.components.resource.StandardResourceReferenceFactory; +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.expression.ExpressionLanguageScope; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * An immutable object for holding information about a type of component + * property. + */ +public final class PropertyDescriptor implements Comparable<PropertyDescriptor> { + + public static final PropertyDescriptor NULL_DESCRIPTOR = new PropertyDescriptor.Builder().name("").build(); + + /** + * The proper name for the property. This is the primary mechanism of + * comparing equality. + */ + private final String name; + + /** + * The name that should be displayed to user when referencing this property + */ + private final String displayName; + + /** + * And explanation of the meaning of the given property. This description is + * meant to be displayed to a user or simply provide a mechanism of + * documenting intent. + */ + private final String description; + /** + * The default value for this property + */ + private final String defaultValue; + /** + * The allowable values for this property. If empty then the allowable + * values are not constrained + */ + private final List<AllowableValue> allowableValues; + /** + * Determines whether the property is required for this processor + */ + private final boolean required; + /** + * indicates that the value for this property should be considered sensitive + * and protected whenever stored or represented + */ + private final boolean sensitive; + /** + * indicates whether this property well-known for this processor or is + * user-defined + */ + private final boolean dynamic; + /** + * indicates whether or nor this property will evaluate expression language + * against the flow file attributes + */ + private final ExpressionLanguageScope expressionLanguageScope; + /** + * indicates whether or not this property represents resources that should be added + * to the classpath and used for loading native libraries for this instance of the component + */ + private final boolean dynamicallyModifiesClasspath; + + /** + * the interface of the {@link ControllerService} that this Property refers + * to; will be null if not identifying a ControllerService. + */ + private final Class<? extends ControllerService> controllerServiceDefinition; + + /** + * The validators that should be used whenever an attempt is made to set + * this property value. Any allowable values specified will be checked first + * and any validators specified will be ignored. + */ + private final List<Validator> validators; + + /** + * The list of dependencies that this property has on other properties + */ + private final Set<PropertyDependency> dependencies; + + /** + * The definition of the resource(s) that this property references + */ + private final ResourceDefinition resourceDefinition; + + protected PropertyDescriptor(final Builder builder) { + this.displayName = builder.displayName == null ? builder.name : builder.displayName; + this.name = builder.name; + this.description = builder.description; + this.defaultValue = builder.defaultValue; + this.allowableValues = builder.allowableValues == null ? null : List.copyOf(builder.allowableValues); + this.required = builder.required; + this.sensitive = builder.sensitive; + this.dynamic = builder.dynamic; + this.dynamicallyModifiesClasspath = builder.dynamicallyModifiesClasspath; + this.expressionLanguageScope = builder.expressionLanguageScope; + this.controllerServiceDefinition = builder.controllerServiceDefinition; + this.validators = List.copyOf(builder.validators); + this.dependencies = builder.dependencies == null ? Collections.emptySet() : Set.copyOf(builder.dependencies); + this.resourceDefinition = builder.resourceDefinition; + } + + @Override + public int compareTo(final PropertyDescriptor o) { + if (o == null) { + return -1; + } + return getName().compareTo(o.getName()); + } + + /** + * Validates the given input against this property descriptor's validator. + * If this descriptor has a set of allowable values then the given value is + * only checked against the allowable values. + * + * @param input the value to validate + * @param context the context of validation + * @return the result of validating the input + */ + public ValidationResult validate(final String input, final ValidationContext context) { + ValidationResult lastResult = Validator.INVALID.validate(this.name, input, context); + + if (allowableValues != null && !allowableValues.isEmpty()) { + final ConstrainedSetValidator csValidator = new ConstrainedSetValidator(allowableValues); + final ValidationResult csResult = csValidator.validate(this.name, input, context); + + if (csResult.isValid()) { + lastResult = csResult; + } else { + return csResult; + } + } + + final ResourceDefinition resourceDefinition = getResourceDefinition(); + if (resourceDefinition != null) { + final Validator validator = new ResourceDefinitionValidator(resourceDefinition, this.expressionLanguageScope); + final ValidationResult result = validator.validate(this.name, input, context); + if (!result.isValid()) { + return result; + } + + lastResult = result; + } + + for (final Validator validator : validators) { + lastResult = validator.validate(this.name, input, context); + if (!lastResult.isValid()) { + break; + } + } + + if (getControllerServiceDefinition() != null) { + final ControllerService service = context.getControllerServiceLookup().getControllerService(input); + if (service == null) { + return new ValidationResult.Builder() + .input(input) + .subject(getDisplayName()) + .valid(false) + .explanation("Property references a Controller Service that does not exist") + .build(); + } else { + return new ValidationResult.Builder() + .valid(true) + .build(); + } + } + + return lastResult; + } + + public static final class Builder { + + private String displayName = null; + private String name = null; + private String description = ""; + private String defaultValue = null; + private List<AllowableValue> allowableValues = null; + private Set<PropertyDependency> dependencies = null; + private boolean required = false; + private boolean sensitive = false; + private ExpressionLanguageScope expressionLanguageScope = ExpressionLanguageScope.NONE; + private boolean dynamic = false; + private boolean dynamicallyModifiesClasspath = false; + private Class<? extends ControllerService> controllerServiceDefinition; + private ResourceDefinition resourceDefinition; + private List<Validator> validators = new ArrayList<>(); + + public Builder fromPropertyDescriptor(final PropertyDescriptor specDescriptor) { + this.name = specDescriptor.name; + this.displayName = specDescriptor.displayName; + this.description = specDescriptor.description; + this.defaultValue = specDescriptor.defaultValue; + this.allowableValues = specDescriptor.allowableValues == null ? null : new ArrayList<>(specDescriptor.allowableValues); + this.required = specDescriptor.required; + this.sensitive = specDescriptor.sensitive; + this.dynamic = specDescriptor.dynamic; + this.dynamicallyModifiesClasspath = specDescriptor.dynamicallyModifiesClasspath; + this.expressionLanguageScope = specDescriptor.expressionLanguageScope; + this.controllerServiceDefinition = specDescriptor.getControllerServiceDefinition(); + this.validators = new ArrayList<>(specDescriptor.validators); + this.dependencies = new HashSet<>(specDescriptor.dependencies); + this.resourceDefinition = specDescriptor.resourceDefinition; + return this; + } + + /** + * Sets a unique id for the property. This field is optional and if not + * specified the PropertyDescriptor's name will be used as the + * identifying attribute. However, by supplying an id, the + * PropertyDescriptor's name can be changed without causing problems. + * This is beneficial because it allows a User Interface to represent + * the name differently. + * + * @param displayName of the property + * @return the builder + */ + public Builder displayName(final String displayName) { + if (null != displayName) { + this.displayName = displayName; + } + + return this; + } + + /** + * Sets the property name. + * + * @param name of the property + * @return the builder + */ + public Builder name(final String name) { + if (null != name) { + this.name = name; + } + return this; + } + + /** + * Sets the scope of the expression language evaluation + * + * @param expressionLanguageScope scope of the expression language evaluation + * @return the builder + */ + public Builder expressionLanguageSupported(final ExpressionLanguageScope expressionLanguageScope) { + this.expressionLanguageScope = expressionLanguageScope; + return this; + } + + /** + * @param description of the property + * @return the builder + */ + public Builder description(final String description) { + if (null != description) { + this.description = description; + } + return this; + } + + /** + * Specifies the initial value and the default value that will be used if the user does not specify a value. + * <p> + * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) + * and this value is not one of those Allowable Values, an Exception will be thrown. + * If the Allowable Values have been set, the default value should be set to + * the "Value" of the {@link DescribedValue} object (see {@link DescribedValue#getValue()}). + * There's an overload available for this (see {@link #defaultValue(DescribedValue)}). + * + * @param value default value + * @return the builder + */ + public Builder defaultValue(final String value) { + if (null != value) { + this.defaultValue = value; + } + return this; + } + + /** + * Specifies the initial value and the default value that will be used if the user does not specify a value. + * <p> + * Sets the default value to the "Value" of the {@link DescribedValue} object. + * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) + * and this value is not one of those Allowable Values, an Exception will be thrown. + * In case there is not a restricted set of Allowable Values {@link #defaultValue(String)} may be used. + * + * @param value default value holder + * @return the builder + */ + public Builder defaultValue(final DescribedValue value) { + return defaultValue(value != null ? value.getValue() : null); + } + + /** + * Clears the initial value and default value from this Property. + * + * @return the builder + */ + public Builder clearDefaultValue() { + this.defaultValue = null; + return this; + } + + public Builder dynamic(final boolean dynamic) { + this.dynamic = dynamic; + return this; + } + + /** + * Specifies that the value of this property represents one or more resources that the + * framework should add to the classpath of as well as consider when looking for native + * libraries for the given component. + * <p> + * NOTE: If a component contains a PropertyDescriptor where dynamicallyModifiesClasspath is set to true, + * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will + * be loaded by a separate InstanceClassLoader for each processor instance.<br/> + * It also allows to load native libraries from the extra classpath. + * <p> + * One can choose to omit the annotation. In this case the loading of native libraries from the extra classpath + * is not supported. + * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an + * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually + * to load required classes on an instance-by-instance basis + * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example). + * <p> + * Any property descriptor that dynamically modifies the classpath should also make use of the {@link #identifiesExternalResource(ResourceCardinality, ResourceType, ResourceType...)} method + * to indicate that the property descriptor references external resources and optionally restrict which types of resources and how many resources the property allows. + * + * @param dynamicallyModifiesClasspath whether or not this property should be used by the framework to modify the classpath + * @return the builder + */ + public Builder dynamicallyModifiesClasspath(final boolean dynamicallyModifiesClasspath) { + this.dynamicallyModifiesClasspath = dynamicallyModifiesClasspath; + return this; + } + + /** + * Sets the Allowable Values for this Property. + * + * @param values constrained set of values + * @return the builder + */ + public Builder allowableValues(final Set<String> values) { + if (null != values) { + this.allowableValues = values.stream().map(AllowableValue::new).toList(); + } + return this; + } + + /** + * Sets the Allowable Values for this Property. + * <p> + * Uses the {@link Enum#name()} of each value as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param <E> Type of Allowable Value + * @param values constrained set of values + * @return the builder + */ + public <E extends Enum<E>> Builder allowableValues(final E[] values) { + if (null != values) { + this.allowableValues = Arrays.stream(values) + .map(enumValue -> enumValue instanceof DescribedValue describedValue + ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) + .toList(); + } + return this; + } + + /** + * Sets the Allowable Values for this Property. + * <p> + * Uses the {@link Enum#name()} of each value from {@link Class#getEnumConstants()} as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param enumClass an enum class that contains a set of values and optionally implements the DescribedValue interface + * @param <E> generic parameter for an enum class, that may implement the DescribedValue interface + * @return the builder + */ + public <E extends Enum<E>> Builder allowableValues(final Class<E> enumClass) { + return allowableValues(enumClass.getEnumConstants()); + } + + /** + * Sets the Allowable Values for this Property. + * <p> + * Uses the {@link Enum#name()} of each value of the {@link EnumSet} as "Value" for the {@link AllowableValue}. + * In case the enum value is a {@link DescribedValue}, uses the information provided instead + * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). + * + * @param enumValues an enum set that contains a set of values and optionally implements the DescribedValue interface + * @param <E> generic parameter for an enum class, that may implement the DescribedValue interface + * @return the builder + */ + public <E extends Enum<E>> Builder allowableValues(final EnumSet<E> enumValues) { + if (null != enumValues) { + this.allowableValues = enumValues.stream() + .map(enumValue -> enumValue instanceof DescribedValue describedValue + ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) + .toList(); + } + return this; + } + + /** + * Sets the Allowable Values for this Property. + * + * @param values constrained set of values + * @return the builder + */ + public Builder allowableValues(final String... values) { + if (null != values) { + this.allowableValues = Arrays.stream(values).map(AllowableValue::new).toList(); + } + return this; + } + + /** + * Sets the Allowable Values for this Property. + * <p> + * Uses the information provided by each {@link DescribedValue} (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, + * {@link DescribedValue#getDescription()}) to populate the {@link AllowableValue}s. + * + * @param values constrained set of values + * @return the builder + */ + public Builder allowableValues(final DescribedValue... values) { + if (null != values) { + this.allowableValues = Arrays.stream(values).map(AllowableValue::fromDescribedValue).toList(); + } + return this; + } + + /** + * Clears all Allowable Values from this Property + * + * @return the builder + */ + public Builder clearAllowableValues() { + this.allowableValues = null; + return this; + } + + /** + * @param required true if yes; false otherwise + * @return the builder + */ + public Builder required(final boolean required) { + this.required = required; + return this; + } + + /** + * @param sensitive true if sensitive; false otherwise + * @return the builder + */ + public Builder sensitive(final boolean sensitive) { + this.sensitive = sensitive; + return this; + } + + /** + * @param validator for the property + * @return the builder + */ + public Builder addValidator(final Validator validator) { + if (validator != null) { + validators.add(validator); + } + return this; + } + + /** + * Clear all Validators from this Property + * + * @return the builder + */ + public Builder clearValidators() { + validators.clear(); + return this; + } + + /** + * Specifies that this property provides the identifier of a Controller + * Service that implements the given interface + * + * @param controllerServiceDefinition the interface that is implemented + * by the Controller Service + * @return the builder + */ + public Builder identifiesControllerService(final Class<? extends ControllerService> controllerServiceDefinition) { + if (controllerServiceDefinition != null) { + this.controllerServiceDefinition = controllerServiceDefinition; + } + return this; + } + + private boolean isValueAllowed(final String value) { + if (allowableValues == null || value == null) { + return true; + } + + for (final AllowableValue allowableValue : allowableValues) { + if (allowableValue.getValue().equals(value)) { + return true; + } + } + + return false; + } + + /** + * Specifies that this property references one or more resources that are external to NiFi that the component is meant to consume. + * Any property descriptor that identifies an external resource will be automatically validated against the following rules: + * <ul> + * <li>If the ResourceCardinality is SINGLE, the given property value must be a file, a directory, or a URL that uses a protocol of http/https/file.</li> + * <li>The given resourceTypes dictate which types of input are allowed. For example, if <code>identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE)</code> + * is used, the input must be a regular file. If <code>identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY)</code> is used, then the input + * must be exactly one file OR directory. + * </li> + * <li>If the ResourceCardinality is MULTIPLE, the given property value may consist of one or more resources, each separted by a comma and optional white space.</li> + * </ul> + * <p> + * Generally, any property descriptor that makes use of the {@link #dynamicallyModifiesClasspath(boolean)} method to dynamically update its classpath should also + * make use of this method, specifying which types of resources are allowed and how many. + * + * @param cardinality specifies how many resources the property should allow + * @param resourceType the type of resource that is allowed + * @param additionalResourceTypes if more than one type of resource is allowed, any resource type in addition to the given resource type may be provided + * @return the builder + */ + public Builder identifiesExternalResource(final ResourceCardinality cardinality, final ResourceType resourceType, final ResourceType... additionalResourceTypes) { + Objects.requireNonNull(cardinality); + Objects.requireNonNull(resourceType); + + final Set<ResourceType> resourceTypes = new HashSet<>(); + resourceTypes.add(resourceType); + resourceTypes.addAll(Arrays.asList(additionalResourceTypes)); + + this.resourceDefinition = new StandardResourceDefinition(cardinality, resourceTypes); + return this; + } + + /** + * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a non-null value. + * Furthermore, if one or more explicit Allowable Values are provided, this Property will not be relevant unless the given Property's value is equal to one of the given Allowable Values. + * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. + * <p> + * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given + * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for + * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component + * will still be valid, because the value of this property is irrelevant. + * <p> + * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. + * + * @param property the property that must be set in order for this property to become relevant + * @param dependentValues the possible values for the given property for which this Property is relevant + * @return the builder + */ + public Builder dependsOn(final PropertyDescriptor property, final AllowableValue... dependentValues) { + if (dependencies == null) { + dependencies = new HashSet<>(); + } + + if (dependentValues.length == 0) { + dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName())); + } else { + final Set<String> dependentValueSet = new HashSet<>(); + for (final AllowableValue value : dependentValues) { + dependentValueSet.add(value.getValue()); + } + + dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName(), dependentValueSet)); + } + + return this; + } + + /** + * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given + * <code>String</code> arguments. + * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. + * <p> + * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given + * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for + * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component + * will still be valid, because the value of this property is irrelevant. + * <p> + * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. + * + * @param property the property that must be set in order for this property to become relevant + * @param firstDependentValue the first value for the given property for which this Property is relevant + * @param additionalDependentValues any other values for the given property for which this Property is relevant + * @return the builder + */ + public Builder dependsOn(final PropertyDescriptor property, final String firstDependentValue, final String... additionalDependentValues) { + final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1]; + dependentValues[0] = new AllowableValue(firstDependentValue); + int i = 1; + for (final String additionalDependentValue : additionalDependentValues) { + dependentValues[i++] = new AllowableValue(additionalDependentValue); + } + + return dependsOn(property, dependentValues); + } + + /** + * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given + * {@link DescribedValue} arguments. + * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. + * <p> + * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given + * Described Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for + * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component + * will still be valid, because the value of this property is irrelevant. + * <p> + * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. + * + * @param property the property that must be set in order for this property to become relevant + * @param firstDependentValue the first value for the given property for which this Property is relevant + * @param additionalDependentValues any other values for the given property for which this Property is relevant + * @return the builder + */ + public Builder dependsOn(final PropertyDescriptor property, final DescribedValue firstDependentValue, final DescribedValue... additionalDependentValues) { + final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1]; + dependentValues[0] = AllowableValue.fromDescribedValue(firstDependentValue); + int i = 1; + for (final DescribedValue additionalDependentValue : additionalDependentValues) { + dependentValues[i++] = AllowableValue.fromDescribedValue(additionalDependentValue); + } + + return dependsOn(property, dependentValues); + } + + /** + * Clear all Dependencies from this Property + * + * @return the builder + */ + public Builder clearDependsOn() { + this.dependencies = new HashSet<>(); + return this; + } + + /** + * @return a PropertyDescriptor as configured + * @throws IllegalStateException if allowable values are configured but + * no default value is set, or the default value is not contained within + * the allowable values. + */ + public PropertyDescriptor build() { + if (name == null) { + throw new IllegalStateException("Must specify a name"); + } + if (!isValueAllowed(defaultValue)) { + throw new IllegalStateException("Default value [" + defaultValue + "] is not in the set of allowable values"); + } + + return new PropertyDescriptor(this); + } + } + + public String getDisplayName() { + return displayName; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getDefaultValue() { + return defaultValue; + } + + public boolean isRequired() { + return required; + } + + public boolean isSensitive() { + return sensitive; + } + + public boolean isDynamic() { + return dynamic; + } + + public boolean isExpressionLanguageSupported() { + return !expressionLanguageScope.equals(ExpressionLanguageScope.NONE); + } + + public ExpressionLanguageScope getExpressionLanguageScope() { + return expressionLanguageScope; + } + + public boolean isDynamicClasspathModifier() { + return dynamicallyModifiesClasspath; + } + + public Class<? extends ControllerService> getControllerServiceDefinition() { + return controllerServiceDefinition; + } + + public List<Validator> getValidators() { + return validators; + } + + public List<AllowableValue> getAllowableValues() { + return allowableValues; + } + + public Set<PropertyDependency> getDependencies() { + return dependencies; + } + + public ResourceDefinition getResourceDefinition() { + return resourceDefinition; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other instanceof PropertyDescriptor otherPropertyDescriptor) { + return this.name.equals(otherPropertyDescriptor.name); + } + + return false; + } + + @Override + public int hashCode() { + return 287 + this.name.hashCode() * 47; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + displayName + "]"; + } + + private static final class ConstrainedSetValidator implements Validator { + + private static final String POSITIVE_EXPLANATION = "Given value found in allowed set"; + private static final String NEGATIVE_EXPLANATION = "Given value not found in allowed set '%1$s'"; + private static final String VALUE_DELIMITER = ", "; + private final String validStrings; + private final Collection<String> validValues; + + /** + * Constructs a validator that will check if the given value is in the + * given set. + * + * @param validValues values which are acceptible + * @throws NullPointerException if the given validValues is null + */ + private ConstrainedSetValidator(final Collection<AllowableValue> validValues) { + this.validValues = validValues.stream().map(AllowableValue::getValue).toList(); + this.validStrings = String.join(VALUE_DELIMITER, this.validValues); + } + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final ValidationResult.Builder builder = new ValidationResult.Builder(); + builder.input(input); + builder.subject(subject); + if (validValues.contains(input)) { + builder.valid(true); + builder.explanation(POSITIVE_EXPLANATION); + } else { + builder.valid(false); + builder.explanation(String.format(NEGATIVE_EXPLANATION, validStrings)); + } + return builder.build(); + } + } + + private static class ResourceDefinitionValidator implements Validator { + private final ResourceDefinition resourceDefinition; + private final ExpressionLanguageScope expressionLanguageScope; + + public ResourceDefinitionValidator(final ResourceDefinition resourceDefinition, final ExpressionLanguageScope expressionLanguageScope) { + this.resourceDefinition = resourceDefinition; + this.expressionLanguageScope = expressionLanguageScope; + } + + @Override + public ValidationResult validate(final String subject, final String configuredInput, final ValidationContext context) { + final ValidationResult.Builder resultBuilder = new ValidationResult.Builder() + .input(configuredInput) + .subject(subject); + + if (configuredInput == null) { + return resultBuilder.valid(false) + .explanation("No value specified") + .build(); + } + + // If Expression Language is supported and is used in the property value, we cannot perform validation against the configured + // input unless the Expression Language is expressly limited to only env/syst properties variables. In that case, we can evaluate + // it and then validate the value after evaluating the Expression Language. + String input = configuredInput; + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(configuredInput)) { + if (expressionLanguageScope != null && expressionLanguageScope == ExpressionLanguageScope.ENVIRONMENT) { + input = context.newPropertyValue(configuredInput).evaluateAttributeExpressions().getValue(); + resultBuilder.input(input); + } else { + return resultBuilder.valid(true) + .explanation("Expression Language is present, so validation of property value cannot be performed") + .build(); + } + } + + // If the property can be text, then there's nothing to validate. Anything that is entered may be valid. + // This will be improved in the future, by allowing the user to specify the type of resource that is being referenced. + // Until then, we will simply require that the component perform any necessary validation. + final boolean allowsText = resourceDefinition.getResourceTypes().contains(ResourceType.TEXT); + if (allowsText) { + return resultBuilder.valid(true) + .explanation("Property allows for Resource Type of Text, so validation of property value cannot be performed") + .build(); + } + + final String[] splits = input.split(","); + if (resourceDefinition.getCardinality() == ResourceCardinality.SINGLE && splits.length > 1) { + return resultBuilder.valid(false) + .explanation("Property only supports a single Resource but " + splits.length + " resources were specified") + .build(); + } + + final Set<ResourceType> resourceTypes = resourceDefinition.getResourceTypes(); + final List<String> nonExistentResources = new ArrayList<>(); + + int count = 0; + for (final String split : splits) { + final ResourceReference resourceReference = new StandardResourceReferenceFactory().createResourceReference(split, resourceDefinition); + if (resourceReference == null) { + continue; + } + + count++; + + final boolean accessible = resourceReference.isAccessible(); + if (!accessible) { + nonExistentResources.add(resourceReference.getLocation()); + continue; + } + + if (!resourceTypes.contains(resourceReference.getResourceType())) { + return resultBuilder.valid(false) + .explanation("Specified Resource is a " + resourceReference.getResourceType().name() + " but this property does not allow this type of resource") + .build(); + } + } + + if (count == 0) { + return resultBuilder.valid(false) + .explanation("No resources were specified") + .build(); + } + + if (!nonExistentResources.isEmpty()) { + return resultBuilder.valid(false) + .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentResources) + .build(); + } + + return resultBuilder.valid(true) + .build(); + } + } +}
diff --git a/src/main/java/org/apache/nifi/components/PropertyValue.java b/src/main/java/org/apache/nifi/components/PropertyValue.java new file mode 100644 index 0000000..4e52c54 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/PropertyValue.java
@@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.nifi.components.resource.ResourceReference; +import org.apache.nifi.components.resource.ResourceReferences; +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.expression.AttributeValueDecorator; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.DataUnit; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.registry.EnvironmentVariables; + +/** + * <p> + * A PropertyValue provides a mechanism whereby the currently configured value + * of a processor property can be obtained in different forms. + * </p> + */ +public interface PropertyValue { + + /** + * @return the raw property value as a string + */ + String getValue(); + + /** + * @return an integer representation of the property value, or + * <code>null</code> if not set + * @throws NumberFormatException if not able to parse + */ + Integer asInteger(); + + /** + * @return a Long representation of the property value, or <code>null</code> + * if not set + * @throws NumberFormatException if not able to parse + */ + Long asLong(); + + /** + * @return a Boolean representation of the property value, or + * <code>null</code> if not set + */ + Boolean asBoolean(); + + /** + * @return a Float representation of the property value, or + * <code>null</code> if not set + * @throws NumberFormatException if not able to parse + */ + Float asFloat(); + + /** + * @return a Double representation of the property value, of + * <code>null</code> if not set + * @throws NumberFormatException if not able to parse + */ + Double asDouble(); + + /** + * @param timeUnit specifies the TimeUnit to convert the time duration into + * @return a Long value representing the value of the configured time period + * in terms of the specified TimeUnit; if the property is not set, returns + * <code>null</code> + */ + Long asTimePeriod(TimeUnit timeUnit); + + /** + * Returns the value as a Duration + * + * @return a Duration representing the value, or <code>null</code> if the value is unset + */ + Duration asDuration(); + + /** + * + * @param dataUnit specifies the DataUnit to convert the data size into + * @return a Long value representing the value of the configured data size + * in terms of the specified DataUnit; if hte property is not set, returns + * <code>null</code> + */ + Double asDataSize(DataUnit dataUnit); + + /** + * @return the ControllerService whose identifier is the raw value of + * <code>this</code>, or <code>null</code> if either the value is not set or + * the value does not identify a ControllerService + */ + ControllerService asControllerService(); + + /** + * @param <T> the generic type of the controller service + * @param serviceType the class of the Controller Service + * @return the ControllerService whose identifier is the raw value of the + * <code>this</code>, or <code>null</code> if either the value is not set or + * the value does not identify a ControllerService. The object returned by + * this method is explicitly cast to type specified, if the type specified + * is valid. Otherwise, throws an IllegalArgumentException + * + * @throws IllegalArgumentException if the value of <code>this</code> points + * to a ControllerService but that service is not of type + * <code>serviceType</code> or if <code>serviceType</code> references a + * class that is not an interface + */ + <T extends ControllerService> T asControllerService(Class<T> serviceType) throws IllegalArgumentException; + + /** + * @return a ResourceReference for the configured property value, or <code>null</code> if no value was specified, or if the property references multiple resources. + * @see #asResources() + */ + ResourceReference asResource(); + + /** + * @return a ResourceReferences for the configured property value. If no property value is set, a ResourceReferences will be returned that references no resources. + * I.e., this method will never return <code>null</code>. + */ + ResourceReferences asResources(); + + /** + * Returns the property value as one of the configured allowableValues, see {@link PropertyDescriptor.Builder#allowableValues(Class)} + * <p> + * The {@link Class#getEnumConstants()} of the provided enum are searched for an entry matching the value of <code>this</code>. + * In case an enum value is a {@link DescribedValue}, uses the defined {@link DescribedValue#getValue()} for comparison. + * Otherwise, the {@link Enum#name()} is used. + * + * @param <E> the generic type of the enum used as allowableValues + * @param enumType the class of the enum used as allowableValues + * @return the matching enum entry, or <code>null</code> if the value is not set. + * @throws IllegalArgumentException if no enum entry matching the value of <code>this</code> is found. + */ + <E extends Enum<E>> E asAllowableValue(Class<E> enumType) throws IllegalArgumentException; + + /** + * @return <code>true</code> if the user has configured a value, or if the + * {@link PropertyDescriptor} for the associated property has a default + * value, <code>false</code> otherwise + */ + boolean isSet(); + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language; + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @return a PropertyValue with the new value is returned, supporting call + * chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions() throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language; + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param attributes a Map of attributes that the Expression can reference. + * These will take precedence over any underlying env/syst properties values. + * + * @return a PropertyValue with the new value + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(Map<String, String> attributes) throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language. + * The supplied decorator is then given a chance to decorate the value, and + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param attributes a Map of attributes that the Expression can reference. + * These will take precedence over any variables in any underlying variable + * registry. + * @param decorator the decorator to use in order to update the values + * returned after variable substitution and expression language evaluation. + * + * @return a PropertyValue with the new value + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(Map<String, String> attributes, AttributeValueDecorator decorator) throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language; + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param flowFile to evaluate attributes of. It's flow file properties and + * then flow file attributes will take precedence over any underlying + * env/syst properties. + * @return a PropertyValue with the new value is returned, supporting call + * chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(FlowFile flowFile) throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language; + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param flowFile to evaluate attributes of. It's flow file properties and + * then flow file attributes will take precedence over any underlying + * env/syst properties. + * @param additionalAttributes a Map of additional attributes that the + * Expression can reference. These attributes will take precedence over any + * conflicting attributes in the provided flowfile or any underlying + * env/syst properties. + * + * @return a PropertyValue with the new value is returned, supporting call + * chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes) throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language; + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param flowFile to evaluate attributes of. It's flow file properties and + * then flow file attributes will take precedence over any underlying + * env/syst properties. + * @param additionalAttributes a Map of additional attributes that the + * Expression can reference. These attributes will take precedence over any + * conflicting attributes in the provided flowfile or any underlying + * env/syst properties. + * @param decorator the decorator to use in order to update the values + * returned after variable substitution and expression language evaluation. + * + * @return a PropertyValue with the new value is returned, supporting call + * chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes, AttributeValueDecorator decorator) throws ProcessException; + + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression + * Language; a PropertyValue with the new value is then returned, supporting + * call chaining. + * </p> + * + * @param flowFile to evaluate attributes of + * @param additionalAttributes a Map of additional attributes that the Expression can reference. If entries in + * this Map conflict with entries in the FlowFile's attributes, the entries in this Map are given a higher priority. + * @param decorator the decorator to use in order to update the values returned by the Expression Language + * @param stateValues a Map of the state values to be referenced explicitly by specific state accessing functions + * + * @return a PropertyValue with the new value is returned, supporting call + * chaining + * + * @throws ProcessException if the Expression cannot be compiled or evaluating + * the Expression against the given attributes causes an Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes, AttributeValueDecorator decorator, Map<String, String> stateValues) + throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language. + * The supplied decorator is then given a chance to decorate the value, and + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param decorator the decorator to use in order to update the values + * returned after variable substitution and expression language evaluation. + * @return a PropertyValue with the new value is then returned, supporting + * call chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(AttributeValueDecorator decorator) throws ProcessException; + + /** + * <p> + * Replaces values in the Property Value using the NiFi Expression Language. + * The supplied decorator is then given a chance to decorate the value, and + * a PropertyValue with the new value is then returned, supporting call + * chaining. Before executing the expression language statement any + * variables names found within any underlying {@link EnvironmentVariables} will + * be substituted with their values. + * </p> + * + * @param flowFile to evaluate expressions against + * @param decorator the decorator to use in order to update the values + * returned after variable substitution and expression language evaluation. + * + * + * @return a PropertyValue with the new value is then returned, supporting + * call chaining + * + * @throws ProcessException if the Expression cannot be compiled or + * evaluating the Expression against the given attributes causes an + * Exception to be thrown + */ + PropertyValue evaluateAttributeExpressions(FlowFile flowFile, AttributeValueDecorator decorator) throws ProcessException; + + /** + * <p> + * Indicates whether the value of the property uses Expression Language. + * </p> + * + * @return <code>true</code> if the property value makes use of the Expression Language, <code>false</code> otherwise. + */ + boolean isExpressionLanguagePresent(); +}
diff --git a/src/main/java/org/apache/nifi/components/RequiredPermission.java b/src/main/java/org/apache/nifi/components/RequiredPermission.java new file mode 100644 index 0000000..81c9ded --- /dev/null +++ b/src/main/java/org/apache/nifi/components/RequiredPermission.java
@@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.util.Arrays; + +/** + * Enumeration of permission required for annotated components + */ +public enum RequiredPermission { + READ_FILESYSTEM("read-filesystem", "read filesystem"), + WRITE_FILESYSTEM("write-filesystem", "write filesystem"), + READ_DISTRIBUTED_FILESYSTEM("read-distributed-filesystem", "read distributed filesystem"), + WRITE_DISTRIBUTED_FILESYSTEM("write-distributed-filesystem", "write distributed filesystem"), + EXECUTE_CODE("execute-code", "execute code"), + ACCESS_KEYTAB("access-keytab", "access keytab"), + ACCESS_TICKET_CACHE("access-ticket-cache", "access ticket cache"), + ACCESS_ENVIRONMENT_CREDENTIALS("access-environment-credentials", "access environment credentials"), + EXPORT_NIFI_DETAILS("export-nifi-details", "export nifi details"), + REFERENCE_REMOTE_RESOURCES("reference-remote-resources", "reference remote resources"); + + private String permissionIdentifier; + private String permissionLabel; + + RequiredPermission(String permissionIdentifier, String permissionLabel) { + this.permissionIdentifier = permissionIdentifier; + this.permissionLabel = permissionLabel; + } + + public String getPermissionIdentifier() { + return permissionIdentifier; + } + + public String getPermissionLabel() { + return permissionLabel; + } + + public static RequiredPermission valueOfPermissionIdentifier(final String permissionIdentifier) { + return Arrays.stream(values()) + .filter(candidate -> candidate.getPermissionIdentifier().equals(permissionIdentifier)) + .findFirst().orElse(null); + } +}
diff --git a/src/main/java/org/apache/nifi/components/ValidationContext.java b/src/main/java/org/apache/nifi/components/ValidationContext.java new file mode 100644 index 0000000..031d39a --- /dev/null +++ b/src/main/java/org/apache/nifi/components/ValidationContext.java
@@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.context.PropertyContext; +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.expression.ExpressionLanguageCompiler; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public interface ValidationContext extends PropertyContext { + + /** + * @return the {@link ControllerServiceLookup} which can be used to obtain + * Controller Services + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @param controllerService to lookup the validation context of + * @return a ValidationContext that is appropriate for validating the given + * {@link ControllerService} + */ + ValidationContext getControllerServiceValidationContext(ControllerService controllerService); + + /** + * @return a new {@link ExpressionLanguageCompiler} that can be used to + * compile and evaluate Attribute Expressions + */ + ExpressionLanguageCompiler newExpressionLanguageCompiler(); + + /** + * @param value to make a PropertyValue object for + * @return a PropertyValue that represents the given value + */ + PropertyValue newPropertyValue(String value); + + /** + * @return a Map of all configured Properties + */ + Map<PropertyDescriptor, String> getProperties(); + + /** + * @return the currently configured Annotation Data + */ + String getAnnotationData(); + + /** + * There are times when the framework needs to consider a component valid, + * even if it references an invalid ControllerService. This method will + * return <code>false</code> if the component is to be considered valid even + * if the given Controller Service is referenced and is invalid. + * + * @param service to check if validation is required + * @return <code>false</code> if the component is to be considered valid + * even if the given Controller Service is referenced and is invalid + */ + boolean isValidationRequired(ControllerService service); + + /** + * @param value to test whether expression language is present + * @return <code>true</code> if the given value contains a NiFi Expression + * Language expression, <code>false</code> if it does not + */ + boolean isExpressionLanguagePresent(String value); + + /** + * @param propertyName to test whether expression language is supported + * @return <code>true</code> if the property with the given name supports + * the NiFi Expression Language, <code>false</code> if the property does not + * support the Expression Language or is not a valid property name + */ + boolean isExpressionLanguageSupported(String propertyName); + + /** + * Returns the identifier of the ProcessGroup that the component being validated lives in + * + * @return the identifier of the ProcessGroup that the component being validated lives in + */ + String getProcessGroupIdentifier(); + + /** + * Returns a Collection containing the names of all Parameters that are referenced by the property with the given name + * + * @param propertyName Name of property for which to get referenced parameters + * @return a Collection containing the names of all Parameters that are referenced by the property with the given name + */ + Collection<String> getReferencedParameters(String propertyName); + + /** + * @param parameterName the name of the Parameter + * @return <code>true</code> if a Parameter with the given name is defined in the currently selected Parameter Context + */ + boolean isParameterDefined(String parameterName); + + /** + * Returns <code>true</code> if a Parameter with the given name is defined and has a non-null value, <code>false</code> if either the Parameter + * is not defined or the Parameter is defined but has a value of <code>null</code>. + * @param parameterName the name of the parameter + * @return <code>true</code> if the Parameter is defined and has a non-null value, false otherwise + */ + boolean isParameterSet(String parameterName); + + /** + * Determines whether or not the dependencies of the given Property Descriptor are satisfied. + * If the given Property Descriptor has no dependency on another property, then the dependency is satisfied. + * If there is at least one dependency, then all dependencies must be satisfied. + * In order for a dependency to be considered satisfied, all of the following must be true: + * <ul> + * <li>The property that is depended upon has all of its dependencies satisfied.</li> + * <li>If the given Property Descriptor depends on a given AllowableValue, then the property that is depended upon has a value that falls within the given range of Allowable Values for + * the dependency.</li> + * </ul> + * + * @param propertyDescriptor the property descriptor + * @param propertyDescriptorLookup a lookup for converting from a property name to the property descriptor with that name + * @return <code>true</code> if all dependencies of the given property descriptor are satisfied, <code>false</code> otherwise + */ + default boolean isDependencySatisfied(PropertyDescriptor propertyDescriptor, Function<String, PropertyDescriptor> propertyDescriptorLookup) { + return isDependencySatisfied(propertyDescriptor, propertyDescriptorLookup, new HashSet<>()); + } + + private boolean isDependencySatisfied(final PropertyDescriptor propertyDescriptor, final Function<String, PropertyDescriptor> propertyDescriptorLookup, final Set<String> propertiesSeen) { + final Set<PropertyDependency> dependencies = propertyDescriptor.getDependencies(); + if (dependencies.isEmpty()) { + return true; + } + + final boolean added = propertiesSeen.add(propertyDescriptor.getName()); + if (!added) { + return false; + } + + try { + for (final PropertyDependency dependency : dependencies) { + final String dependencyName = dependency.getPropertyName(); + + // Check if the property being depended upon has its dependencies satisfied. + final PropertyDescriptor dependencyDescriptor = propertyDescriptorLookup.apply(dependencyName); + if (dependencyDescriptor == null) { + return false; + } + + final PropertyValue propertyValue = getProperty(dependencyDescriptor); + final String dependencyValue = propertyValue == null ? dependencyDescriptor.getDefaultValue() : propertyValue.getValue(); + if (dependencyValue == null) { + return false; + } + + final boolean transitiveDependencySatisfied = isDependencySatisfied(dependencyDescriptor, propertyDescriptorLookup, propertiesSeen); + if (!transitiveDependencySatisfied) { + return false; + } + + // Check if the property being depended upon is set to one of the values that satisfies this dependency. + // If the dependency has no dependent values, then any non-null value satisfies the dependency. + // The value is already known to be non-null due to the check above. + final Set<String> dependentValues = dependency.getDependentValues(); + if (dependentValues != null && !dependentValues.contains(dependencyValue)) { + return false; + } + } + + return true; + } finally { + propertiesSeen.remove(propertyDescriptor.getName()); + } + } + + /** + * Determines whether or not incoming and outgoing connections should be validated. + * If <code>true</code>, then the validation should verify that all Relationships either have one or more connections that include the Relationship, + * or that the Relationship is auto-terminated. + * Additionally, if <code>true</code>, then any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_REQUIRED} + * should be invalid unless it has an incoming (non-looping) connection, and any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_FORBIDDEN} + * should be invalid if it does have any incoming connection. + * + * @return <code>true</code> if Connections should be validated, <code>false</code> if Connections should be ignored + */ + default boolean isValidateConnections() { + return true; + } +}
diff --git a/src/main/java/org/apache/nifi/components/ValidationResult.java b/src/main/java/org/apache/nifi/components/ValidationResult.java new file mode 100644 index 0000000..ca23c1d --- /dev/null +++ b/src/main/java/org/apache/nifi/components/ValidationResult.java
@@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import java.util.Objects; + +/** + * Immutable - thread safe + */ +public class ValidationResult { + + private final String subject; + private final String input; + private final String explanation; + private final boolean valid; + + protected ValidationResult(final Builder builder) { + this.subject = builder.subject; + this.input = builder.input; + this.explanation = builder.explanation; + this.valid = builder.valid; + } + + /** + * @return true if current result is valid; false otherwise + */ + public boolean isValid() { + return this.valid; + } + + /** + * @return this input value that was tested for validity + */ + public String getInput() { + return this.input; + } + + /** + * @return an explanation of the validation result + */ + public String getExplanation() { + return this.explanation; + } + + /** + * @return the item being validated + */ + public String getSubject() { + return this.subject; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 79 * hash + Objects.hashCode(this.subject); + hash = 79 * hash + Objects.hashCode(this.input); + hash = 79 * hash + Objects.hashCode(this.explanation); + hash = 79 * hash + (this.valid ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ValidationResult other = (ValidationResult) obj; + if (!Objects.equals(this.subject, other.subject)) { + return false; + } + if (!Objects.equals(this.input, other.input)) { + return false; + } + if (!Objects.equals(this.explanation, other.explanation)) { + return false; + } + if (this.valid != other.valid) { + return false; + } + return true; + } + + @Override + public String toString() { + if (input == null) { + return String.format("'%s' is %s because %s", subject, valid ? "valid" : "invalid", explanation); + } else { + return String.format("'%s' validated against '%s' is %s because %s", subject, input, valid ? "valid" : "invalid", explanation); + } + } + + public static final class Builder { + + private boolean valid = false; + private String input = null; + private String explanation = ""; + private String subject = ""; + + /** + * Defaults to false + * + * @param valid true if is valid; false otherwise + * @return the builder + */ + public Builder valid(final boolean valid) { + this.valid = valid; + return this; + } + + /** + * Defaults to empty string + * + * @param input what was validated + * @return the builder + */ + public Builder input(final String input) { + if (null != input) { + this.input = input; + } + return this; + } + + /** + * Defaults to empty string + * + * @param explanation of validation result + * @return the builder + */ + public Builder explanation(final String explanation) { + if (null != explanation) { + this.explanation = explanation; + } + return this; + } + + /** + * Defaults to empty string + * + * @param subject the thing that was validated + * @return the builder + */ + public Builder subject(final String subject) { + if (null != subject) { + this.subject = subject; + } + return this; + } + + public ValidationResult build() { + return new ValidationResult(this); + } + } +}
diff --git a/src/main/java/org/apache/nifi/components/Validator.java b/src/main/java/org/apache/nifi/components/Validator.java new file mode 100644 index 0000000..babdc41 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/Validator.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +public interface Validator { + + /** + * Validator object providing validation behavior in which validation always + * fails + */ + Validator INVALID = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + return new ValidationResult.Builder().subject(subject).explanation(String.format("'%s' is not a supported property or has no Validator associated with it", subject)).input(input).build(); + } + }; + + /** + * Validator object providing validation behavior in which validation always + * passes + */ + Validator VALID = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } + }; + + /** + * @param subject what is being validated + * @param input the string to be validated + * @param context the ValidationContext to use when validating properties + * @return ValidationResult + * @throws NullPointerException of given input is null + */ + ValidationResult validate(String subject, String input, ValidationContext context); +}
diff --git a/src/main/java/org/apache/nifi/components/VersionedComponent.java b/src/main/java/org/apache/nifi/components/VersionedComponent.java new file mode 100644 index 0000000..53fc62c --- /dev/null +++ b/src/main/java/org/apache/nifi/components/VersionedComponent.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components; + +import java.util.Optional; + +public interface VersionedComponent { + + /** + * @return the unique identifier that maps this component to a component that is versioned + * in a Flow Registry or has been imported, or <code>Optional.empty</code> if this component has not + * been saved to a Flow Registry or imported. + */ + Optional<String> getVersionedComponentId(); + + /** + * Updates the versioned component identifier + * + * @param versionedComponentId the identifier of the versioned component + * + * @throws IllegalStateException if this component is already under version control with a different ID and + * the given ID is not null + */ + void setVersionedComponentId(String versionedComponentId); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java b/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java new file mode 100644 index 0000000..65fc167 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java
@@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; + +public class FileResourceReference implements ResourceReference { + private final File file; + private final ResourceType resourceType; + + public FileResourceReference(final File file) { + this.file = Objects.requireNonNull(file); + this.resourceType = file.isDirectory() ? ResourceType.DIRECTORY : ResourceType.FILE; + } + + @Override + public File asFile() { + return file; + } + + @Override + public URL asURL() { + try { + return file.toURI().toURL(); + } catch (final MalformedURLException e) { + throw new AssertionError("File " + file.getAbsolutePath() + " cannot be represented as a URL"); // we won't encounter this. + } + } + + @Override + public InputStream read() throws IOException { + if (resourceType != ResourceType.FILE) { + throw new FileNotFoundException("Could not read from file with name " + file.getAbsolutePath() + " because that references a directory"); + } + + return new FileInputStream(file); + } + + @Override + public boolean isAccessible() { + return file.exists() && file.canRead(); + } + + @Override + public String getLocation() { + return file.getAbsolutePath(); + } + + @Override + public ResourceType getResourceType() { + return resourceType; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final FileResourceReference that = (FileResourceReference) o; + return Objects.equals(file, that.file) + && resourceType == that.resourceType; + } + + @Override + public int hashCode() { + return Objects.hash(file, resourceType); + } + + @Override + public String toString() { + return "FileResourceReference[file=" + file + ", resourceType=" + resourceType + "]"; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java b/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java new file mode 100644 index 0000000..1563df7 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java
@@ -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. + */ + +package org.apache.nifi.components.resource; + +/** + * Indicates the cardinality of how many resources can be referenced by a given property. + */ +public enum ResourceCardinality { + + /** + * Exactly one resource must be specified + */ + SINGLE, + + /** + * One or more resources may be supplied, as a comma-separated list + */ + MULTIPLE; +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceContext.java b/src/main/java/org/apache/nifi/components/resource/ResourceContext.java new file mode 100644 index 0000000..bf02c59 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceContext.java
@@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import org.apache.nifi.components.PropertyDescriptor; + +/** + * Provides the context in which a Reference Reference is to be evaluated + */ +public interface ResourceContext { + /** + * @return a ResourceReferenceFactory that can be used to parse a property value into a {@link ResourceReference} or {@link ResourceReferences} + */ + ResourceReferenceFactory getResourceReferenceFactory(); + + /** + * @return the PropertyDescriptor that describes the property whose value may be a resource + */ + PropertyDescriptor getPropertyDescriptor(); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.java b/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.java new file mode 100644 index 0000000..b91065e --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.java
@@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.util.Set; + +/** + * Defines the number and types of resources that allowed to be referenced by a component property + */ +public interface ResourceDefinition { + + /** + * Specifies the number of resources that should be reference-able by a component property + * @return the cardinality for the number of resources that should be referenced + */ + ResourceCardinality getCardinality(); + + /** + * @return the types of resources that are allowed to be referenced + */ + Set<ResourceType> getResourceTypes(); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReference.java b/src/main/java/org/apache/nifi/components/resource/ResourceReference.java new file mode 100644 index 0000000..49b95d3 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceReference.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * A reference to a Resource that is identified by a property value + */ +public interface ResourceReference { + + /** + * @return a file representation of the resource, or <code>null</code> if the Resource cannot be represented as a File + */ + File asFile(); + + /** + * @return a URL representation of the resource, or <code>null</code> if the Resource cannot be represented as a URL + */ + URL asURL(); + + /** + * @return an InputStream to read the contents of the resource + * + * @throws IOException if unable to obtain an InputStream from the resource + */ + InputStream read() throws IOException; + + /** + * Indicates whether or not the resource is accessible. What it means for the resource to be accessible depends on the type of + * resource. A File resource, for example, might be accessible only if the file exists and is readable, while a URL resource might + * always be considered accessible, or might be accesssible only if the existence of the resource can be confirmed. + * + * @return <code>true</code> if the file can be accessed, <code>false</code> otherwise + */ + boolean isAccessible(); + + /** + * @return a String representation of the location, or <code>null</code> for a Resource that does not have an external location. + * For a File or a Directory, this will be the full path name; for a URL it will be the String form of the URL + */ + String getLocation(); + + /** + * @return the type of resource that is being referenced + */ + ResourceType getResourceType(); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java b/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java new file mode 100644 index 0000000..67b056e --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java
@@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +public interface ResourceReferenceFactory { + ResourceReference createResourceReference(String value, ResourceDefinition resourceDefinition); + + ResourceReferences createResourceReferences(String value, ResourceDefinition resourceDefinition); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java b/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java new file mode 100644 index 0000000..5addba0 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java
@@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.net.URL; +import java.util.List; + +/** + * A representation of zero or more {@link ResourceReference}s + */ +public interface ResourceReferences { + + /** + * @return a List representation of all Resource References + */ + List<ResourceReference> asList(); + + /** + * @return a list of all Resource References' locations + */ + List<String> asLocations(); + + /** + * @return a list of all Resource References' URLs + */ + List<URL> asURLs(); + + /** + * @return the number of Resource References held + */ + int getCount(); + + /** + * Iterates through the Resource References and for any reference that may represent more than one + * resource, flattens the resource into a List of single-entity references. For example, consider that this ResourceReferences + * holds a single ResourceReference, of type DIRECTORY and the referenced directory contains 10 files. Calling {@link #asList()} would + * return a single ResourceReference. But calling <code>flatten()</code> would return a new ResourceReferences type whose {@link #asList()} + * method would return 10 ResourceReference objects, each with a ResourceType of FILE. The flatten operation is not recursive, meaning that if + * a DIRECTORY is flattened, any sub-directories will be dropped. If the contents of the subdirectories are to be retained, use {@link #flattenRecursively()} + * instead. + * + * @return a flattened ResourceReferences + */ + ResourceReferences flatten(); + + /** + * Recursively iterates through the Resource References and for any reference that may represent more than one + * resource, flattens the resource into a List of single-entity references. For example, consider that this ResourceReferences + * holds a single ResourceReference, of type DIRECTORY and the referenced directory contains 10 files. Calling {@link #asList()} would + * return a single ResourceReference. But calling <code>flatten()</code> would return a new ResourceReferences type whose {@link #asList()} + * method would return 10 ResourceReference objects, each with a ResourceType of FILE. The flatten operation is recursive, meaning that if + * a DIRECTORY is encountered, its reference will be replaced with a new reference for each file, even if that file exists 100 levels deep + * in the directory structure. + * + * @return a flattened ResourceReferences + */ + ResourceReferences flattenRecursively(); +}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceType.java b/src/main/java/org/apache/nifi/components/resource/ResourceType.java new file mode 100644 index 0000000..60eff90 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/ResourceType.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +public enum ResourceType { + /** + * Referenced Resource is a File on a local (or mounted) file system + */ + FILE("file"), + + /** + * Referenced Resource is a directory on a local (or mounted) file system + */ + DIRECTORY("directory"), + + /** + * Referenced Resource is UTF-8 text, rather than an external entity + */ + TEXT("text"), + + /** + * Referenced Resource is a URL that uses the HTTP, HTTPS, or file protocol + * (i.e., <code>http://...</code>, <code>https://...</code>, or <code>file:...</code>) + */ + URL("URL"); + + private final String prettyPrintName; + ResourceType(final String prettyPrintName) { + this.prettyPrintName = prettyPrintName; + } + + @Override + public String toString() { + return prettyPrintName; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java new file mode 100644 index 0000000..4e1fa6b --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import org.apache.nifi.components.PropertyDescriptor; + +public class StandardResourceContext implements ResourceContext { + private final ResourceReferenceFactory resourceReferenceFactory; + private final PropertyDescriptor propertyDescriptor; + + public StandardResourceContext(final ResourceReferenceFactory resourceReferenceFactory, final PropertyDescriptor propertyDescriptor) { + this.resourceReferenceFactory = resourceReferenceFactory; + this.propertyDescriptor = propertyDescriptor; + } + + @Override + public ResourceReferenceFactory getResourceReferenceFactory() { + return resourceReferenceFactory; + } + + @Override + public PropertyDescriptor getPropertyDescriptor() { + return propertyDescriptor; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java new file mode 100644 index 0000000..125dcbf --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.util.Set; + +public class StandardResourceDefinition implements ResourceDefinition { + private final ResourceCardinality cardinality; + private final Set<ResourceType> resourceTypes; + + public StandardResourceDefinition(final ResourceCardinality cardinality, final Set<ResourceType> resourceTypes) { + this.cardinality = cardinality; + this.resourceTypes = resourceTypes; + } + + @Override + public ResourceCardinality getCardinality() { + return cardinality; + } + + @Override + public Set<ResourceType> getResourceTypes() { + return resourceTypes; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java new file mode 100644 index 0000000..3980a9b --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java
@@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class StandardResourceReferenceFactory implements ResourceReferenceFactory { + + private static final ResourceReferences EMPTY_RESOURCE_REFERENCES = new StandardResourceReferences(Collections.emptyList()); + + public ResourceReferences createResourceReferences(final String value, final ResourceDefinition resourceDefinition) { + if (value == null) { + return EMPTY_RESOURCE_REFERENCES; + } + + final String trimmed = value.trim(); + if (trimmed.isEmpty()) { + return EMPTY_RESOURCE_REFERENCES; + } + + if (resourceDefinition == null) { + return EMPTY_RESOURCE_REFERENCES; + } + + final List<ResourceReference> references; + final List<String> locations = parseResourceLocations(value); + references = new ArrayList<>(locations.size()); + locations.forEach(location -> references.add(createResourceReference(location, resourceDefinition))); + + return new StandardResourceReferences(references); + } + + public ResourceReference createResourceReference(final String value, final ResourceDefinition resourceDefinition) { + if (value == null) { + return null; + } + + final String trimmed = value.trim(); + if (trimmed.isEmpty()) { + return null; + } + + if (resourceDefinition == null) { + return null; + } + + final Set<ResourceType> allowedResourceTypes = resourceDefinition.getResourceTypes(); + if (allowedResourceTypes.contains(ResourceType.URL)) { + try { + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + return new URLResourceReference(URI.create(trimmed).toURL()); + } + + if (trimmed.startsWith("file:")) { + final URL url = URI.create(trimmed).toURL(); + final String filename = url.getFile(); + final File file = new File(filename); + return new FileResourceReference(file); + } + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL: " + trimmed); + } + } + + final boolean fileAllowed = allowedResourceTypes.contains(ResourceType.FILE) || allowedResourceTypes.contains(ResourceType.DIRECTORY); + final boolean textAllowed = allowedResourceTypes.contains(ResourceType.TEXT); + + if (fileAllowed && textAllowed) { + // We have to make a determination whether this is a file or text. Eventually, it will be best if the user tells us explicitly. + // For now, we will make a determination based on a couple of simple rules. + final File file = new File(trimmed); + if (file.isAbsolute() || file.exists()) { + return new FileResourceReference(file); + } + + if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) { + return new FileResourceReference(file); + } + + return new Utf8TextResource(value); // Use explicit value, not trimmed value, as the white space may be important for textual content. + } + + if (fileAllowed) { + final File file = new File(trimmed); + return new FileResourceReference(file); + } + + if (textAllowed) { + return new Utf8TextResource(value); + } + + return null; + } + + private List<String> parseResourceLocations(final String rawValue) { + final List<String> resourceLocations = new ArrayList<>(); + final String[] splits = rawValue.split(","); + for (final String split : splits) { + final String trimmed = split.trim(); + if (trimmed.isEmpty()) { + continue; + } + + resourceLocations.add(trimmed); + } + + return resourceLocations; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java new file mode 100644 index 0000000..11ce057 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java
@@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class StandardResourceReferences implements ResourceReferences { + public final List<ResourceReference> resourceReferences; + + public StandardResourceReferences(final List<ResourceReference> resourceReferences) { + this.resourceReferences = new ArrayList<>(Objects.requireNonNull(resourceReferences)); + } + + @Override + public List<ResourceReference> asList() { + return Collections.unmodifiableList(resourceReferences); + } + + @Override + public List<String> asLocations() { + final List<String> locations = new ArrayList<>(resourceReferences.size()); + resourceReferences.forEach(ref -> locations.add(ref.getLocation())); + return locations; + } + + @Override + public List<URL> asURLs() { + final List<URL> locations = new ArrayList<>(resourceReferences.size()); + resourceReferences.forEach(ref -> locations.add(ref.asURL())); + return locations; + } + + @Override + public int getCount() { + return resourceReferences.size(); + } + + @Override + public ResourceReferences flatten() { + if (resourceReferences.isEmpty()) { + return this; + } + + final List<ResourceReference> flattened = new ArrayList<>(); + resourceReferences.forEach(reference -> { + if (reference.getResourceType() == ResourceType.DIRECTORY) { + addChildren(reference.asFile(), flattened); + } else { + flattened.add(reference); + } + }); + + return new StandardResourceReferences(flattened); + } + + private void addChildren(final File file, final List<ResourceReference> flattened) { + if (file == null) { + return; + } + + if (file.isDirectory()) { + final File[] children = file.listFiles(); + if (children != null) { + for (final File child : children) { + if (child.isFile()) { + flattened.add(new FileResourceReference(child)); + } + } + } + } else { + flattened.add(new FileResourceReference(file)); + } + } + + + @Override + public ResourceReferences flattenRecursively() { + if (resourceReferences.isEmpty()) { + return this; + } + + final List<ResourceReference> flattened = new ArrayList<>(); + resourceReferences.forEach(reference -> { + if (reference.getResourceType() == ResourceType.DIRECTORY) { + recurse(reference.asFile(), flattened); + } else { + flattened.add(reference); + } + }); + + return new StandardResourceReferences(flattened); + } + + private void recurse(final File file, final List<ResourceReference> flattened) { + if (file == null) { + return; + } + + if (file.isDirectory()) { + final File[] children = file.listFiles(); + if (children != null) { + for (final File child : children) { + recurse(child, flattened); + } + } + } else { + flattened.add(new FileResourceReference(file)); + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final StandardResourceReferences that = (StandardResourceReferences) o; + return Objects.equals(resourceReferences, that.resourceReferences); + } + + @Override + public int hashCode() { + return Objects.hash(resourceReferences); + } + + @Override + public String toString() { + return "StandardResourceReferences[resources=" + resourceReferences + "]"; + } +}
diff --git a/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java b/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java new file mode 100644 index 0000000..8b5f7f7 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java
@@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class URLResourceReference implements ResourceReference { + private final URL url; + + public URLResourceReference(final URL url) { + this.url = url; + } + + @Override + public File asFile() { + return null; + } + + @Override + public URL asURL() { + return url; + } + + @Override + public InputStream read() throws IOException { + return url.openStream(); + } + + @Override + public boolean isAccessible() { + return true; + } + + @Override + public String getLocation() { + return url.toExternalForm(); + } + + @Override + public ResourceType getResourceType() { + return ResourceType.URL; + } + + @Override + public String toString() { + return "URLResourceReference[url=" + url + "]"; + } + +}
diff --git a/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java b/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java new file mode 100644 index 0000000..78f94e9 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java
@@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class Utf8TextResource implements ResourceReference { + private final String text; + + public Utf8TextResource(final String text) { + this.text = text; + } + + @Override + public File asFile() { + return null; + } + + @Override + public URL asURL() { + return null; + } + + @Override + public InputStream read() throws IOException { + return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public boolean isAccessible() { + return true; + } + + @Override + public String getLocation() { + return null; + } + + @Override + public ResourceType getResourceType() { + return ResourceType.TEXT; + } + + @Override + public String toString() { + return "Utf8TextResource[text=" + text.length() + " characters]"; + } +}
diff --git a/src/main/java/org/apache/nifi/components/state/Scope.java b/src/main/java/org/apache/nifi/components/state/Scope.java new file mode 100644 index 0000000..dd0d0aa --- /dev/null +++ b/src/main/java/org/apache/nifi/components/state/Scope.java
@@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.state; + +/** + * A Scope represents how a NiFi component's state is to be stored and retrieved when running in a cluster. + */ +public enum Scope { + + /** + * State is to be treated as "global" across the cluster. I.e., the same component on all nodes will + * have access to the same state. + */ + CLUSTER, + + /** + * State is to be treated local to the node. I.e., the same component will have different state on each + * node in the cluster. + */ + LOCAL; + + @Override + public String toString() { + return name(); + } +}
diff --git a/src/main/java/org/apache/nifi/components/state/StateManager.java b/src/main/java/org/apache/nifi/components/state/StateManager.java new file mode 100644 index 0000000..1669ea1 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/state/StateManager.java
@@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.state; + +import java.io.IOException; +import java.util.Map; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.components.state.exception.StateTooLargeException; + +/** + * <p> + * The StateManager is responsible for providing NiFi components a mechanism for storing + * and retrieving state. + * </p> + * + * <p> + * When calling methods in this class, the {@link Scope} is used in order to specify whether + * state should be stored/retrieved from the local state or the clustered state. However, if + * any instance of NiFi is not clustered (or is disconnected from its cluster), the Scope is + * not really relevant and the local state will be used. This allows component + * developers to not concern themselves with whether or not a particular instance of NiFi is + * clustered. Instead, developers should assume that the instance is indeed clustered and write + * the component accordingly. If not clustered, the component will still behave in the same + * manner, as a standalone node could be thought of as a "cluster of 1." + * </p> + * + * <p> + * This mechanism is designed to allow developers to easily store and retrieve small amounts of state. + * The storage mechanism is implementation-specific, but is typically either stored on a remote system + * or on disk. For that reason, one should consider the cost of storing and retrieving this data, and the + * amount of data should be kept to the minimum required. + * </p> + * + * <p> + * Any component that wishes to use the StateManager should also use the {@link Stateful} annotation to provide + * a description of what state is being stored and what Scope is used. If this annotation is not present, the UI + * will not expose such information or allow DFMs to clear the state. + * </p> + */ +public interface StateManager { + + /** + * Updates the value of the component's state, setting it to given value + * + * @param state the value to change the state to + * @param scope the scope to use when storing the state + * + * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + void setState(Map<String, String> state, Scope scope) throws IOException; + + /** + * Returns the current state for the component. This return value will never be <code>null</code>. + * If the state has not yet been set, the StateMap's version will be -1, and the map of values will be empty. + * + * @param scope the scope to use when fetching the state + * @return the current state for the component + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + StateMap getState(Scope scope) throws IOException; + + /** + * Updates the value of the component's state to the new value if and only if the value currently + * is the same as the given oldValue. + * + * @param oldValue the old value to compare against + * @param newValue the new value to use if and only if the state's value is the same as the given oldValue + * @param scope the scope to use for storing the new state + * @return <code>true</code> if the state was updated to the new value, <code>false</code> if the state's value was not + * equal to oldValue + * + * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + boolean replace(StateMap oldValue, Map<String, String> newValue, Scope scope) throws IOException; + + /** + * Clears all keys and values from the component's state + * + * @param scope the scope whose values should be cleared + * + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + void clear(Scope scope) throws IOException; +}
diff --git a/src/main/java/org/apache/nifi/components/state/StateMap.java b/src/main/java/org/apache/nifi/components/state/StateMap.java new file mode 100644 index 0000000..95829a7 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/state/StateMap.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.state; + +import java.util.Map; +import java.util.Optional; + +/** + * Provides a representation of a component's state at some point in time. + */ +public interface StateMap { + + /** + * Get state version is not guaranteed to be numeric, but can be used to compare against an expected version. + * The default implementation uses the available version number and considers -1 as indicating an empty version + * + * @return State version or empty when not known + */ + Optional<String> getStateVersion(); + + /** + * Returns the value associated with the given key + *n + * @param key the key whose value should be retrieved + * @return the value associated with the given key, or <code>null</code> if no value is associated + * with this key. + */ + String get(String key); + + /** + * Returns an immutable Map representation of all keys and values for the state of a component. + * + * @return an immutable Map representation of all keys and values for the state of a component. + */ + Map<String, String> toMap(); +}
diff --git a/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java b/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java new file mode 100644 index 0000000..41c8b24 --- /dev/null +++ b/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java
@@ -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. + */ + +package org.apache.nifi.components.state.exception; + +import java.io.IOException; +import org.apache.nifi.components.state.StateManager; + +/** + * Thrown when attempting to store state via the {@link StateManager} but the state being + * stored is larger than is allowed by the backing storage mechanism. + */ +public class StateTooLargeException extends IOException { + private static final long serialVersionUID = 1L; + + public StateTooLargeException(final String message) { + super(message); + } +}
diff --git a/src/main/java/org/apache/nifi/context/ClusterContext.java b/src/main/java/org/apache/nifi/context/ClusterContext.java new file mode 100644 index 0000000..aba5244 --- /dev/null +++ b/src/main/java/org/apache/nifi/context/ClusterContext.java
@@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.context; + +/** + * A context for retrieving information about the state of the cluster. + */ +public interface ClusterContext { + + /** + * Retrieves the current state of the cluster connection of this node. + * + * @return True if this node is connected to the cluster. False otherwise. + */ + boolean isConnectedToCluster(); +}
diff --git a/src/main/java/org/apache/nifi/context/PropertyContext.java b/src/main/java/org/apache/nifi/context/PropertyContext.java new file mode 100644 index 0000000..89ffb45 --- /dev/null +++ b/src/main/java/org/apache/nifi/context/PropertyContext.java
@@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.context; + +import java.util.Map; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; + +/** + * A context for retrieving a PropertyValue from a PropertyDescriptor. + */ +public interface PropertyContext { + + /** + * Retrieves the current value set for the given descriptor, if a value is + * set - else uses the descriptor to determine the appropriate default value + * + * @param descriptor to lookup the value of + * @return the property value of the given descriptor + */ + PropertyValue getProperty(PropertyDescriptor descriptor); + + + Map<String, String> getAllProperties(); + +}
diff --git a/src/main/java/org/apache/nifi/controller/AbstractControllerService.java b/src/main/java/org/apache/nifi/controller/AbstractControllerService.java new file mode 100644 index 0000000..30773ee --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/AbstractControllerService.java
@@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import org.apache.nifi.annotation.lifecycle.OnDisabled; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.reporting.InitializationException; + +public abstract class AbstractControllerService extends AbstractConfigurableComponent implements ControllerService { + + private String identifier; + private ControllerServiceLookup serviceLookup; + private ComponentLog logger; + private StateManager stateManager; + private volatile ConfigurationContext configurationContext; + private volatile boolean enabled = false; + private NodeTypeProvider nodeTypeProvider; + + @Override + public final void initialize(final ControllerServiceInitializationContext context) throws InitializationException { + this.identifier = context.getIdentifier(); + serviceLookup = context.getControllerServiceLookup(); + logger = context.getLogger(); + stateManager = context.getStateManager(); + nodeTypeProvider = context.getNodeTypeProvider(); + init(context); + } + + @Override + public String getIdentifier() { + return identifier; + } + + /** + * @return the {@link ControllerServiceLookup} that was passed to the + * {@link #init(ControllerServiceInitializationContext)} method + */ + protected final ControllerServiceLookup getControllerServiceLookup() { + return serviceLookup; + } + + /** + * @return the {@link NodeTypeProvider} that was passed to the + * {@link #init(ControllerServiceInitializationContext)} method + */ + protected final NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + /** + * Provides a mechanism by which subclasses can perform initialization of + * the Controller Service before it is scheduled to be run + * + * @param config of initialization context + * @throws InitializationException if unable to init + */ + protected void init(final ControllerServiceInitializationContext config) throws InitializationException { + } + + @OnEnabled + public final void enabled() { + this.enabled = true; + } + + @OnDisabled + public final void disabled() { + this.enabled = false; + } + + public boolean isEnabled() { + return this.enabled; + } + + /** + * @return the logger that has been provided to the component by the + * framework in its initialize method + */ + protected ComponentLog getLogger() { + return logger; + } + + /** + * @return the StateManager that can be used to store and retrieve state for this Controller Service + */ + protected StateManager getStateManager() { + return stateManager; + } + + @OnEnabled + public final void abstractStoreConfigContext(final ConfigurationContext configContext) { + this.configurationContext = configContext; + } + + @OnDisabled + public final void abstractClearConfigContext() { + this.configurationContext = null; + } + + protected ConfigurationContext getConfigurationContext() { + final ConfigurationContext context = this.configurationContext; + if (context == null) { + throw new IllegalStateException("No Configuration Context exists"); + } + + return configurationContext; + } + + protected PropertyValue getProperty(final PropertyDescriptor descriptor) { + return getConfigurationContext().getProperty(descriptor); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/ConfigurationContext.java b/src/main/java/org/apache/nifi/controller/ConfigurationContext.java new file mode 100644 index 0000000..2886a2a --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.context.PropertyContext; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * This context is passed to ControllerServices and Reporting Tasks in order + * to expose their configuration to them. + */ +public interface ConfigurationContext extends PropertyContext { + + /** + * @return an unmodifiable map of all configured properties for this + * {@link ControllerService} + */ + Map<PropertyDescriptor, String> getProperties(); + + /** + * @return the annotation data configured for the component + */ + String getAnnotationData(); + + /** + * @return a String representation of the scheduling period, or <code>null</code> if + * the component does not have a scheduling period (e.g., for ControllerServices) + */ + String getSchedulingPeriod(); + + /** + * Returns the amount of time, in the given {@link TimeUnit} that will + * elapsed between the return of one execution of the + * component's <code>onTrigger</code> method and + * the time at which the method is invoked again. This method will return + * null if the component does not have a scheduling period (e.g., for ControllerServices) + * + * @param timeUnit unit of time for scheduling + * @return period of time or <code>null</code> if component does not have a scheduling + * period + */ + Long getSchedulingPeriod(TimeUnit timeUnit); + + /** + * Returns the component's (ControllerService, ReportingTask, ParameterProvider, e.g.) name + * @return the String name of this component + */ + String getName(); +}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerService.java b/src/main/java/org/apache/nifi/controller/ControllerService.java new file mode 100644 index 0000000..e8f9850 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/ControllerService.java
@@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.flowanalysis.FlowAnalysisRule; +import org.apache.nifi.migration.PropertyConfiguration; +import org.apache.nifi.parameter.ParameterProvider; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSessionFactory; +import org.apache.nifi.processor.Processor; +import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.reporting.ReportingTask; + +/** + * <p> + * This interface provides a mechanism for creating services that are shared + * among all {@link Processor}s, {@link ReportingTask}s, {@link FlowAnalysisRule}s, {@link ParameterProvider}s and other + * {@code ControllerService}s. + * </p> + * + * <p> + * <code>ControllerService</code>s are discovered using Java's + * <code>ServiceLoader</code> mechanism. As a result, all implementations must + * follow these rules: + * </p> + * + * <ul> + * <li>The implementation must implement this interface.</li> + * <li>The implementation must have a file named + * org.apache.nifi.controller.ControllerService located within the jar's + * <code>META-INF/services</code> directory. This file contains a list of + * fully-qualified class names of all <code>ControllerService</code>s in the + * jar, one-per-line. + * <li>The implementation must support a default constructor.</li> + * </ul> + * + * <p> + * <b>All implementations of this interface must be thread-safe.</b> + * </p> + * + * <h2>Accessing Controller Services</h2> + * <p> + * A ControllerService is accessible only through its interface. The framework + * provides access to a ControllerService through two different mechanisms: + * <ul> + * <li> + * A {@link PropertyDescriptor} can be created via the + * {@link PropertyDescriptor.Builder} after calling the + * {@link PropertyDescriptor.Builder#identifiesControllerService(Class) identifiesControllerService(Class)} + * method and then the ControllerService is accessed via + * {@link PropertyValue#asControllerService(Class)} method. + * <p> + * For example: + * </p> + * <pre><code> + * public static final PropertyDescriptor MY_PROPERTY = new PropertyDescriptor.Builder() + * .name("My Property") + * .description("Example Property") + * .identifiesControllerService( MyControllerServiceInterface.class ) + * .build(); + * + * ... + * public void onTrigger(ProcessContext context, ProcessSessionFactory sessionFactory) throws ProcessException { + * // Obtain the user-selected controller service + * final MyControllerServiceInterface service = context.getProperty(MY_PROPERTY).asControllerService( MyControllerServiceInterface.class ); + * ... + * } + * + * </code></pre> + * <li>A Controller Service can be obtained via a + * {@link ControllerServiceLookup}. This lookup may be obtained, for example, + * from the {@link ProcessContext} that is provided to a {@link Processor}'s + * {@link Processor#onTrigger(ProcessContext, ProcessSessionFactory) onTrigger} + * method. + * <p> + * For example: + * </p> + * + * <pre><code> + * public void onTrigger(ProcessContext context, ProcessSessionFactory sessionFactory) throws ProcessException { + * final MyControllerServiceInterface service = (MyControllerServiceInterface) context.getControllerServiceLookup().getControllerService("service_identifier"); + * } + * </code></pre> + * </li> + * </ul> + * + * <h2>Defining a Controller Service</h2> + * <p> + * Note in both of the examples above, that the Controller Service was accessed + * only by its interface, and this interface extends ControllerService. If we + * have an implementation named MyServiceImpl, for example, that implements + * MyControllerServiceInterface, we cannot, in either case, attempt to cast the + * ControllerService to the desired implementation. Doing so will result in a + * {@link ClassCastException}. This is by design and is done for the following + * reasons: + * + * <ul> + * <li> + * It is a good coding practice to implement such a service as an interface in + * general. + * </li> + * <li> + * A Controller Service can be referenced from different NiFi Archives (NARs). + * This means that the Controller Service may be defined in one ClassLoader and + * referenced from another unrelated ClassLoader. In order to account for this, + * NiFi will change the current thread's ClassLoader as appropriate when + * entering the Controller Service's code and revert back to the previous + * ClassLoader after exiting the Controller Service's code. + * </li> + * </ul> + * + * <h2>Controller Services and NARs</h2> + * <p> + * Due to the fact that a Controller Service may be referenced from a different + * NAR than the one in which the implementation lives, it is crucial that both + * the Controller Service's interface and the code referencing the interface + * inherit from the same ClassLoader. This is accomplished by ensuring that the + * NAR that contains the Controller Service interface is the parent (or + * ancestor) of the NAR that references the Controller Service interface. + * </p> + * + * <p> + * Typically, this is done by creating a NAR structure as follows: + * <pre> + * + my-services-api-nar + * +--- service-X-implementation-nar + * +--- service-Y-implementation-nar + * +--- service-Z-implementation-nar + * +--- processor-A-nar + * +--- processor-B-nar + * </pre> + * + * <p> + * In this case, the {@code MyControllerServiceInterface} interface, and any + * other Controller Service interfaces, will be defined in the + * {@code my-services-api-nar} NAR. Implementations are then encapsulated within + * the {@code service-X-implementation-nar}, + * {@code service-Y-implementation-nar}, and + * {@code service-Z-implementation-nar} NARs. All Controller Services and all + * Processors defined in these NARs are able to reference any other Controller + * Services whose interfaces are provided in the {@code my-services-api-nar} + * NAR. + * </p> + * + * <p> + * For more information on NARs, see the NiFi Developer Guide. + * </p> + */ +public interface ControllerService extends ConfigurableComponent { + + /** + * Provides the Controller Service with access to objects that may be of use + * throughout the life of the service. This method will be called before any + * properties are set + * + * @param context of initialization + * @throws org.apache.nifi.reporting.InitializationException if unable to init + */ + void initialize(ControllerServiceInitializationContext context) throws InitializationException; + + /** + * Indicates whether this controller service, configured with the given {@link ConfigurationContext}, stores state. + * @param context provides access to convenience methods for obtaining property values + * @return True if this controller service stores state + */ + default boolean isStateful(ConfigurationContext context) { + return this.getClass().isAnnotationPresent(Stateful.class); + } + + /** + * <p> + * Allows for the migration of an old property configuration to a new configuration. This allows the Controller Service to evolve over time, + * as it allows properties to be renamed, removed, or reconfigured. + * </p> + * + * <p> + * This method is called only when a Controller Service is restored from a previous configuration. For example, when NiFi is restarted and the + * flow is restored from disk, when a previously configured flow is imported (e.g., from a JSON file that was exported or a NiFi Registry), + * or when a node joins a cluster and inherits a flow that has a new Controller Service. Once called, the method will not be invoked again for this + * Controller Service until NiFi is restarted. + * </p> + * + * @param config the current property configuration + */ + default void migrateProperties(PropertyConfiguration config) { + } + +}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java b/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java new file mode 100644 index 0000000..c778869 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.kerberos.KerberosContext; +import org.apache.nifi.logging.ComponentLog; + +public interface ControllerServiceInitializationContext extends KerberosContext { + + /** + * @return the identifier associated with the {@link ControllerService} with + * which this context is associated + */ + String getIdentifier(); + + /** + * @return the {@link ControllerServiceLookup} which can be used to obtain + * Controller Services + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @return a logger that can be used to log important events in a standard + * way and generate bulletins when appropriate + */ + ComponentLog getLogger(); + + /** + * @return the StateManager that can be used to store and retrieve state for this component + */ + StateManager getStateManager(); + + /** + * @return the {@link NodeTypeProvider} which can be used to detect the node + * type of this NiFi instance. + */ + NodeTypeProvider getNodeTypeProvider(); +}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java b/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java new file mode 100644 index 0000000..0144438 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java
@@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import java.util.Set; + +public interface ControllerServiceLookup { + + /** + * @param serviceIdentifier of controller service + * @return the ControllerService that is registered with the given + * identifier + */ + ControllerService getControllerService(String serviceIdentifier); + + /** + * @param serviceIdentifier identifier of service to check + * @return <code>true</code> if the Controller Service with the given + * identifier is enabled, <code>false</code> otherwise. If the given + * identifier is not known by this ControllerServiceLookup, returns + * <code>false</code> + */ + boolean isControllerServiceEnabled(String serviceIdentifier); + + /** + * @param serviceIdentifier identifier of service to check + * @return <code>true</code> if the Controller Service with the given + * identifier has been enabled but is still in the transitioning state, + * otherwise returns <code>false</code>. If the given identifier is not + * known by this ControllerServiceLookup, returns <code>false</code> + */ + boolean isControllerServiceEnabling(String serviceIdentifier); + + /** + * @param service service to check + * @return <code>true</code> if the given Controller Service is enabled, + * <code>false</code> otherwise. If the given Controller Service is not + * known by this ControllerServiceLookup, returns <code>false</code> + */ + boolean isControllerServiceEnabled(ControllerService service); + + /** + * + * @param serviceType type of service to get identifiers for + * + * @return the set of all Controller Service Identifiers whose Controller + * Service is of the given type. + * @throws IllegalArgumentException if the given class is not an interface + */ + Set<String> getControllerServiceIdentifiers(Class<? extends ControllerService> serviceType) throws IllegalArgumentException; + + /** + * @param serviceIdentifier identifier to look up + * @return the name of the Controller service with the given identifier. If + * no service can be found with this identifier, returns {@code null} + */ + String getControllerServiceName(String serviceIdentifier); +}
diff --git a/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java b/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java new file mode 100644 index 0000000..1552282 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java
@@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.controller; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +/** + * This interface provides a set of methods for checking NiFi node type. + */ +public interface NodeTypeProvider { + + /** + * @return true if this instance is clustered, false otherwise.MockProcessContext + * Clustered means that a node is either connected or trying to connect to the cluster. + */ + boolean isClustered(); + + /** + * @return true if the expected state of clustering is true, false otherwise. Contrary to {{@link #isClustered()}} + * this does not dynamically change with the state of this node. + */ + default boolean isConfiguredForClustering() { + return false; + } + + /** + * @return true if this instances is clustered and connected to the cluster. + */ + default boolean isConnected() { + return false; + } + + /** + * @return true if this instance is the primary node in the cluster; false otherwise + */ + boolean isPrimary(); + + /** + * @return Returns with the hostname of the current node, if clustered. For For a standalone + * NiFi this returns an empty instead. + */ + default Optional<String> getCurrentNode() { + if (isClustered()) { + throw new IllegalStateException("Clustered environment is not handled!"); + } else { + return Optional.empty(); + } + } + + /** + * @return Names/IP addresses of all expected hosts in the cluster (including the current one). For a standalone + * NiFi this returns an empty set instead. + */ + default Set<String> getClusterMembers() { + if (isClustered()) { + throw new IllegalStateException("Clustered environment is not handled!"); + } else { + return Collections.emptySet(); + } + } +}
diff --git a/src/main/java/org/apache/nifi/controller/ScheduledState.java b/src/main/java/org/apache/nifi/controller/ScheduledState.java new file mode 100644 index 0000000..f457a9c --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/ScheduledState.java
@@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +/** + * Indicates the valid values for the state of a <code>Triggerable</code> entity + * with respect to scheduling the entity to run. + */ +public enum ScheduledState { + + /** + * Entity cannot be scheduled to run + */ + DISABLED, + /** + * Entity can be scheduled to run but currently is not + */ + STOPPED, + /** + * Entity is currently scheduled to run + */ + RUNNING, + + STARTING, + + STOPPING, + + RUN_ONCE; +}
diff --git a/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java b/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java new file mode 100644 index 0000000..c9978f8 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.controller; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.components.ConfigVerificationResult; + +import java.util.List; +import java.util.Map; + +/** + * <p> + * Any Controller Service that implements this interface will be provided the opportunity to verify + * a given configuration of the Controller Service. This allows the Controller Service to provide meaningful feedback + * to users when configuring the dataflow. + * </p> + * + * <p> + * Generally speaking, verification differs from validation in that validation is expected to be very + * quick and run often. If a Controller Service is not valid, it cannot be started. However, verification may be + * more expensive or time-consuming to complete. For example, validation may ensure that a username is + * provided for connecting to an external service but should not perform any sort of network connection + * in order to verify that the username is accurate. Verification, on the other hand, may create resources + * such as network connections, may be more expensive to complete, and may be run only when a user invokes + * the action (though verification may later occur at other stages, such as when starting a component). + * </p> + * + * <p> + * Verification is allowed to be run only when a Controller Service is fully disabled. + * Therefore, any initialization logic that may need to be performed + * before the Controller Service is triggered may also be required for verification. However, the framework is not responsible + * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such + * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog, Map)} itself. + * The {@link #verify(ConfigurationContext, ComponentLog, Map)} method will only be called if the configuration is valid according to the + * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid). + * </p> + */ +public interface VerifiableControllerService { + + /** + * Verifies that the configuration defined by the given ConfigurationContext is valid. + * @param context the ProcessContext that contains the necessary configuration + * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result + * in producing bulletins, which can be confusing. + * @param variables a Map of key/value pairs that can be used to resolve variables referenced in property values via Expression Language + * + * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed + */ + List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger, Map<String, String> variables); + +}
diff --git a/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java b/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java new file mode 100644 index 0000000..899bcbc --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java
@@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller; + +import org.apache.nifi.flow.VersionedControllerService; + +public interface VersionedControllerServiceLookup { + + /** + * @param serviceIdentifier of controller service + * @return the VersionedControllerService with the given + * identifier + */ + VersionedControllerService getVersionedControllerService(String serviceIdentifier); +}
diff --git a/src/main/java/org/apache/nifi/controller/queue/QueueSize.java b/src/main/java/org/apache/nifi/controller/queue/QueueSize.java new file mode 100644 index 0000000..ef6a145 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/queue/QueueSize.java
@@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.queue; + +import java.text.NumberFormat; + +public class QueueSize { + private final int objectCount; + private final long totalSizeBytes; + private final int hashCode; + + public QueueSize(final int numberObjects, final long totalSizeBytes) { + if (numberObjects < 0 || totalSizeBytes < 0) { + throw new IllegalArgumentException(); + } + objectCount = numberObjects; + this.totalSizeBytes = totalSizeBytes; + hashCode = (int) (41 + 47 * objectCount + 51 * totalSizeBytes); + } + + /** + * @return number of objects present on the queue + */ + public int getObjectCount() { + return objectCount; + } + + /** + * @return total size in bytes of the content for the data on the queue + */ + public long getByteCount() { + return totalSizeBytes; + } + + /** + * Returns a new QueueSize that is the sum of this QueueSize and the provided QueueSize + * + * @param other the other QueueSize to add to this QueueSize + * @return a new QueueSize that is the sum of this QueueSize and the provided QueueSize + */ + public QueueSize add(final QueueSize other) { + if (other == null) { + return new QueueSize(objectCount, totalSizeBytes); + } + + return new QueueSize(objectCount + other.getObjectCount(), totalSizeBytes + other.getByteCount()); + } + + public QueueSize add(final int count, final long bytes) { + return new QueueSize(objectCount + count, totalSizeBytes + bytes); + } + + @Override + public String toString() { + return "QueueSize[FlowFiles=" + objectCount + ", ContentSize=" + NumberFormat.getNumberInstance().format(totalSizeBytes) + " Bytes]"; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + if (obj == null) { + return false; + } + + if (!(obj instanceof QueueSize)) { + return false; + } + + final QueueSize other = (QueueSize) obj; + return getObjectCount() == other.getObjectCount() && getByteCount() == other.getByteCount(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java b/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java new file mode 100644 index 0000000..bd2cd29 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java
@@ -0,0 +1,313 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +import org.apache.nifi.controller.status.analytics.ConnectionStatusPredictions; +import org.apache.nifi.processor.DataUnit; + +public class ConnectionStatus implements Cloneable { + + private String id; + private String groupId; + private String name; + private String sourceId; + private String sourceName; + private String destinationId; + private String destinationName; + private String backPressureDataSizeThreshold; + private ConnectionStatusPredictions predictions; + private long backPressureBytesThreshold; + private long backPressureObjectThreshold; + private int inputCount; + private long inputBytes; + private int queuedCount; + private long queuedBytes; + private int outputCount; + private long outputBytes; + private int maxQueuedCount; + private long maxQueuedBytes; + private long totalQueuedDuration; + private long maxQueuedDuration; + private FlowFileAvailability flowFileAvailability; + private LoadBalanceStatus loadBalanceStatus; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(final String groupId) { + this.groupId = groupId; + } + + public LoadBalanceStatus getLoadBalanceStatus() { + return loadBalanceStatus; + } + + public void setLoadBalanceStatus(final LoadBalanceStatus loadBalanceStatus) { + this.loadBalanceStatus = loadBalanceStatus; + } + + public int getQueuedCount() { + return queuedCount; + } + + public void setQueuedCount(final int queuedCount) { + this.queuedCount = queuedCount; + } + + public long getQueuedBytes() { + return queuedBytes; + } + + public void setQueuedBytes(final long queuedBytes) { + this.queuedBytes = queuedBytes; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSourceId() { + return sourceId; + } + + public void setSourceId(String sourceId) { + this.sourceId = sourceId; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getDestinationId() { + return destinationId; + } + + public void setDestinationId(String destinationId) { + this.destinationId = destinationId; + } + + public String getDestinationName() { + return destinationName; + } + + public void setDestinationName(String destinationName) { + this.destinationName = destinationName; + } + + public String getBackPressureDataSizeThreshold() { + return backPressureDataSizeThreshold; + } + + public void setBackPressureDataSizeThreshold(String backPressureDataSizeThreshold) { + this.backPressureDataSizeThreshold = backPressureDataSizeThreshold; + setBackPressureBytesThreshold(DataUnit.parseDataSize(backPressureDataSizeThreshold, DataUnit.B).longValue()); + } + + public ConnectionStatusPredictions getPredictions() { + return predictions; + } + + public void setPredictions(ConnectionStatusPredictions predictions) { + this.predictions = predictions; + } + + public long getBackPressureObjectThreshold() { + return backPressureObjectThreshold; + } + + public void setBackPressureObjectThreshold(long backPressureObjectThreshold) { + this.backPressureObjectThreshold = backPressureObjectThreshold; + } + + public long getInputBytes() { + return inputBytes; + } + + public void setInputBytes(long inputBytes) { + this.inputBytes = inputBytes; + } + + public int getInputCount() { + return inputCount; + } + + public void setInputCount(int inputCount) { + this.inputCount = inputCount; + } + + public long getOutputBytes() { + return outputBytes; + } + + public void setOutputBytes(long outputBytes) { + this.outputBytes = outputBytes; + } + + public int getOutputCount() { + return outputCount; + } + + public void setOutputCount(int outputCount) { + this.outputCount = outputCount; + } + + public int getMaxQueuedCount() { + return maxQueuedCount; + } + + public void setMaxQueuedCount(int maxQueuedCount) { + this.maxQueuedCount = maxQueuedCount; + } + + public long getMaxQueuedBytes() { + return maxQueuedBytes; + } + + public void setMaxQueuedBytes(long maxQueueBytes) { + this.maxQueuedBytes = maxQueueBytes; + } + + public long getBackPressureBytesThreshold() { + return backPressureBytesThreshold; + } + + public void setBackPressureBytesThreshold(long backPressureBytesThreshold) { + this.backPressureBytesThreshold = backPressureBytesThreshold; + } + + public long getTotalQueuedDuration() { + return totalQueuedDuration; + } + + public void setTotalQueuedDuration(long totalQueuedDuration) { + this.totalQueuedDuration = totalQueuedDuration; + } + + public long getMaxQueuedDuration() { + return maxQueuedDuration; + } + + public void setMaxQueuedDuration(long maxQueuedDuration) { + this.maxQueuedDuration = maxQueuedDuration; + } + + public FlowFileAvailability getFlowFileAvailability() { + return flowFileAvailability; + } + + public void setFlowFileAvailability(final FlowFileAvailability availability) { + this.flowFileAvailability = availability; + } + + @Override + public ConnectionStatus clone() { + final ConnectionStatus clonedObj = new ConnectionStatus(); + clonedObj.groupId = groupId; + clonedObj.id = id; + clonedObj.loadBalanceStatus = loadBalanceStatus; + clonedObj.inputBytes = inputBytes; + clonedObj.inputCount = inputCount; + clonedObj.name = name; + clonedObj.outputBytes = outputBytes; + clonedObj.outputCount = outputCount; + clonedObj.queuedBytes = queuedBytes; + clonedObj.queuedCount = queuedCount; + clonedObj.sourceId = sourceId; + clonedObj.sourceName = sourceName; + clonedObj.destinationId = destinationId; + clonedObj.destinationName = destinationName; + clonedObj.flowFileAvailability = flowFileAvailability; + + if (predictions != null) { + clonedObj.setPredictions(predictions.clone()); + } + + clonedObj.backPressureDataSizeThreshold = backPressureDataSizeThreshold; + clonedObj.backPressureObjectThreshold = backPressureObjectThreshold; + clonedObj.maxQueuedBytes = maxQueuedBytes; + clonedObj.maxQueuedCount = maxQueuedCount; + clonedObj.totalQueuedDuration = totalQueuedDuration; + clonedObj.maxQueuedDuration = maxQueuedDuration; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ConnectionStatus [id="); + builder.append(id); + builder.append(", groupId="); + builder.append(groupId); + builder.append(", name="); + builder.append(name); + builder.append(", sourceId="); + builder.append(sourceId); + builder.append(", sourceName="); + builder.append(sourceName); + builder.append(", destinationId="); + builder.append(destinationId); + builder.append(", destinationName="); + builder.append(destinationName); + builder.append(", backPressureDataSizeThreshold="); + builder.append(backPressureDataSizeThreshold); + builder.append(", backPressureObjectThreshold="); + builder.append(backPressureObjectThreshold); + builder.append(", flowFileAvailability="); + builder.append(flowFileAvailability); + builder.append(", inputCount="); + builder.append(inputCount); + builder.append(", inputBytes="); + builder.append(inputBytes); + builder.append(", queuedCount="); + builder.append(queuedCount); + builder.append(", queuedBytes="); + builder.append(queuedBytes); + builder.append(", outputCount="); + builder.append(outputCount); + builder.append(", outputBytes="); + builder.append(outputBytes); + builder.append(", maxQueuedCount="); + builder.append(maxQueuedCount); + builder.append(", maxQueueBytes="); + builder.append(maxQueuedBytes); + builder.append(", totalActiveQueuedDuration="); + builder.append(totalQueuedDuration); + builder.append(", maxActiveQueuedDuration="); + builder.append(maxQueuedDuration); + builder.append(", loadBalanceStatus="); + builder.append(loadBalanceStatus); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java b/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java new file mode 100644 index 0000000..d805312 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.controller.status; + +public enum FlowFileAvailability { + ACTIVE_QUEUE_EMPTY, + + HEAD_OF_QUEUE_PENALIZED, + + FLOWFILE_AVAILABLE; +}
diff --git a/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java b/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java new file mode 100644 index 0000000..bd3bed4 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.controller.status; + +public enum LoadBalanceStatus { + LOAD_BALANCE_NOT_CONFIGURED, + + LOAD_BALANCE_ACTIVE, + + LOAD_BALANCE_INACTIVE; +}
diff --git a/src/main/java/org/apache/nifi/controller/status/NodeStatus.java b/src/main/java/org/apache/nifi/controller/status/NodeStatus.java new file mode 100644 index 0000000..0414ee2 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/NodeStatus.java
@@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +import java.util.ArrayList; +import java.util.List; + +/** + * The status of a NiFi node. + */ +public class NodeStatus implements Cloneable { + private long createdAtInMs; + + private long freeHeap; + private long usedHeap; + private long heapUtilization; + private long freeNonHeap; + private long usedNonHeap; + + private long openFileHandlers; + private double processorLoadAverage; + + private long totalThreads; + private long timerDrivenThreads; + + private long flowFileRepositoryFreeSpace; + private long flowFileRepositoryUsedSpace; + + private List<StorageStatus> contentRepositories = new ArrayList<>(); + private List<StorageStatus> provenanceRepositories = new ArrayList<>(); + + public long getCreatedAtInMs() { + return createdAtInMs; + } + + public void setCreatedAtInMs(final long createdAtInMs) { + this.createdAtInMs = createdAtInMs; + } + + public long getFreeHeap() { + return freeHeap; + } + + public void setFreeHeap(final long freeHeap) { + this.freeHeap = freeHeap; + } + + public long getUsedHeap() { + return usedHeap; + } + + public void setUsedHeap(final long usedHeap) { + this.usedHeap = usedHeap; + } + + public long getHeapUtilization() { + return heapUtilization; + } + + public void setHeapUtilization(final long heapUtilization) { + this.heapUtilization = heapUtilization; + } + + public long getFreeNonHeap() { + return freeNonHeap; + } + + public void setFreeNonHeap(final long freeNonHeap) { + this.freeNonHeap = freeNonHeap; + } + + public long getUsedNonHeap() { + return usedNonHeap; + } + + public void setUsedNonHeap(final long usedNonHeap) { + this.usedNonHeap = usedNonHeap; + } + + public long getOpenFileHandlers() { + return openFileHandlers; + } + + public void setOpenFileHandlers(final long openFileHandlers) { + this.openFileHandlers = openFileHandlers; + } + + public double getProcessorLoadAverage() { + return processorLoadAverage; + } + + public void setProcessorLoadAverage(final double processorLoadAverage) { + this.processorLoadAverage = processorLoadAverage; + } + + public long getTotalThreads() { + return totalThreads; + } + + public void setTotalThreads(final long totalThreads) { + this.totalThreads = totalThreads; + } + + public long getTimerDrivenThreads() { + return timerDrivenThreads; + } + + public void setTimerDrivenThreads(final long timerDrivenThreads) { + this.timerDrivenThreads = timerDrivenThreads; + } + + public long getFlowFileRepositoryFreeSpace() { + return flowFileRepositoryFreeSpace; + } + + public void setFlowFileRepositoryFreeSpace(final long flowFileRepositoryFreeSpace) { + this.flowFileRepositoryFreeSpace = flowFileRepositoryFreeSpace; + } + + public long getFlowFileRepositoryUsedSpace() { + return flowFileRepositoryUsedSpace; + } + + public void setFlowFileRepositoryUsedSpace(final long flowFileRepositoryUsedSpace) { + this.flowFileRepositoryUsedSpace = flowFileRepositoryUsedSpace; + } + + public List<StorageStatus> getContentRepositories() { + return contentRepositories; + } + + public void setContentRepositories(final List<StorageStatus> contentRepositories) { + this.contentRepositories = new ArrayList<>(); + this.contentRepositories.addAll(contentRepositories); + } + + public List<StorageStatus> getProvenanceRepositories() { + return provenanceRepositories; + } + + public void setProvenanceRepositories(final List<StorageStatus> provenanceRepositories) { + this.provenanceRepositories = new ArrayList<>(); + this.provenanceRepositories.addAll(provenanceRepositories); + } + + @Override + protected NodeStatus clone() { + final NodeStatus clonedObj = new NodeStatus(); + clonedObj.createdAtInMs = createdAtInMs; + clonedObj.freeHeap = freeHeap; + clonedObj.usedHeap = usedHeap; + clonedObj.heapUtilization = heapUtilization; + clonedObj.freeNonHeap = freeNonHeap; + clonedObj.usedNonHeap = usedNonHeap; + clonedObj.openFileHandlers = openFileHandlers; + clonedObj.processorLoadAverage = processorLoadAverage; + clonedObj.totalThreads = totalThreads; + clonedObj.timerDrivenThreads = timerDrivenThreads; + clonedObj.flowFileRepositoryFreeSpace = flowFileRepositoryFreeSpace; + clonedObj.flowFileRepositoryUsedSpace = flowFileRepositoryUsedSpace; + + final List<StorageStatus> clonedContentRepositories = new ArrayList<>(); + contentRepositories.stream().map(r -> r.clone()).forEach(r -> clonedContentRepositories.add(r)); + clonedObj.contentRepositories = clonedContentRepositories; + + final List<StorageStatus> clonedProvenanceRepositories = new ArrayList<>(); + provenanceRepositories.stream().map(r -> r.clone()).forEach(r -> clonedProvenanceRepositories.add(r)); + clonedObj.provenanceRepositories = clonedProvenanceRepositories; + + return clonedObj; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("NodeStatus{"); + sb.append("createdAtInMs=").append(createdAtInMs); + sb.append(", freeHeap=").append(freeHeap); + sb.append(", usedHeap=").append(usedHeap); + sb.append(", heapUtilization=").append(heapUtilization); + sb.append(", freeNonHeap=").append(freeNonHeap); + sb.append(", usedNonHeap=").append(usedNonHeap); + sb.append(", openFileHandlers=").append(openFileHandlers); + sb.append(", processorLoadAverage=").append(processorLoadAverage); + sb.append(", totalThreads=").append(totalThreads); + sb.append(", timerDrivenThreads=").append(timerDrivenThreads); + sb.append(", flowFileRepositoryFreeSpace=").append(flowFileRepositoryFreeSpace); + sb.append(", flowFileRepositoryUsedSpace=").append(flowFileRepositoryUsedSpace); + sb.append(", contentRepositories=").append(contentRepositories); + sb.append(", provenanceRepositories=").append(provenanceRepositories); + sb.append('}'); + return sb.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/PortStatus.java b/src/main/java/org/apache/nifi/controller/status/PortStatus.java new file mode 100644 index 0000000..0d248cd --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/PortStatus.java
@@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +/** + * The status of a port. + */ +public class PortStatus implements Cloneable { + + private String id; + private String groupId; + private String name; + private Integer activeThreadCount; + private int inputCount; + private long inputBytes; + private int outputCount; + private long outputBytes; + private int flowFilesReceived; + private long bytesReceived; + private int flowFilesSent; + private long bytesSent; + private Boolean transmitting; + private RunStatus runStatus; + + public Boolean isTransmitting() { + return transmitting; + } + + public void setTransmitting(Boolean transmitting) { + this.transmitting = transmitting; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getActiveThreadCount() { + return activeThreadCount; + } + + public void setActiveThreadCount(Integer activeThreadCount) { + this.activeThreadCount = activeThreadCount; + } + + public int getInputCount() { + return inputCount; + } + + public void setInputCount(int inputCount) { + this.inputCount = inputCount; + } + + public long getInputBytes() { + return inputBytes; + } + + public void setInputBytes(long inputBytes) { + this.inputBytes = inputBytes; + } + + public int getOutputCount() { + return outputCount; + } + + public void setOutputCount(final int outputCount) { + this.outputCount = outputCount; + } + + public long getOutputBytes() { + return outputBytes; + } + + public void setOutputBytes(final long outputBytes) { + this.outputBytes = outputBytes; + } + + public RunStatus getRunStatus() { + return runStatus; + } + + public void setRunStatus(RunStatus runStatus) { + this.runStatus = runStatus; + } + + public int getFlowFilesReceived() { + return flowFilesReceived; + } + + public void setFlowFilesReceived(int flowFilesReceived) { + this.flowFilesReceived = flowFilesReceived; + } + + public long getBytesReceived() { + return bytesReceived; + } + + public void setBytesReceived(long bytesReceived) { + this.bytesReceived = bytesReceived; + } + + public int getFlowFilesSent() { + return flowFilesSent; + } + + public void setFlowFilesSent(int flowFilesSent) { + this.flowFilesSent = flowFilesSent; + } + + public long getBytesSent() { + return bytesSent; + } + + public void setBytesSent(long bytesSent) { + this.bytesSent = bytesSent; + } + + public Boolean getTransmitting() { + return transmitting; + } + + @Override + public PortStatus clone() { + final PortStatus clonedObj = new PortStatus(); + clonedObj.id = id; + clonedObj.groupId = groupId; + clonedObj.name = name; + clonedObj.activeThreadCount = activeThreadCount; + clonedObj.inputBytes = inputBytes; + clonedObj.inputCount = inputCount; + clonedObj.outputBytes = outputBytes; + clonedObj.outputCount = outputCount; + clonedObj.flowFilesReceived = flowFilesReceived; + clonedObj.bytesReceived = bytesReceived; + clonedObj.flowFilesSent = flowFilesSent; + clonedObj.bytesSent = bytesSent; + clonedObj.transmitting = transmitting; + clonedObj.runStatus = runStatus; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PortStatus [id="); + builder.append(id); + builder.append(", groupId="); + builder.append(groupId); + builder.append(", name="); + builder.append(name); + builder.append(", activeThreadCount="); + builder.append(activeThreadCount); + builder.append(", transmitting="); + builder.append(transmitting); + builder.append(", inputCount="); + builder.append(inputCount); + builder.append(", inputBytes="); + builder.append(inputBytes); + builder.append(", outputCount="); + builder.append(outputCount); + builder.append(", outputBytes="); + builder.append(outputBytes); + builder.append(", runStatus="); + builder.append(runStatus); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/ProcessGroupStatus.java b/src/main/java/org/apache/nifi/controller/status/ProcessGroupStatus.java new file mode 100644 index 0000000..7aaf6e7 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/ProcessGroupStatus.java
@@ -0,0 +1,694 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +import org.apache.nifi.registry.flow.VersionedFlowState; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ProcessGroupStatus implements Cloneable { + + private String id; + private String name; + private VersionedFlowState versionedFlowState; + private Integer inputCount; + private Long inputContentSize; + private Integer outputCount; + private Long outputContentSize; + private Integer activeThreadCount; + private Integer statelessActiveThreadCount; + private Integer terminatedThreadCount; + private Integer queuedCount; + private Long queuedContentSize; + private Long bytesRead; + private Long bytesWritten; + private int flowFilesReceived; + private long bytesReceived; + private int flowFilesSent; + private long bytesSent; + private int flowFilesTransferred; + private long bytesTransferred; + private long processingNanos; + + private Collection<ConnectionStatus> connectionStatus = new ArrayList<>(); + private Collection<ProcessorStatus> processorStatus = new ArrayList<>(); + private Collection<ProcessGroupStatus> processGroupStatus = new ArrayList<>(); + private Collection<RemoteProcessGroupStatus> remoteProcessGroupStatus = new ArrayList<>(); + private Collection<PortStatus> inputPortStatus = new ArrayList<>(); + private Collection<PortStatus> outputPortStatus = new ArrayList<>(); + + private ProcessingPerformanceStatus processingPerformanceStatus; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public VersionedFlowState getVersionedFlowState() { + return versionedFlowState; + } + + public void setVersionedFlowState(VersionedFlowState versionedFlowState) { + this.versionedFlowState = versionedFlowState; + } + + public Integer getInputCount() { + return inputCount; + } + + public void setInputCount(final Integer inputCount) { + this.inputCount = inputCount; + } + + public Long getInputContentSize() { + return inputContentSize; + } + + public void setInputContentSize(final Long inputContentSize) { + this.inputContentSize = inputContentSize; + } + + public Integer getOutputCount() { + return outputCount; + } + + public void setOutputCount(final Integer outputCount) { + this.outputCount = outputCount; + } + + public Long getOutputContentSize() { + return outputContentSize; + } + + public void setOutputContentSize(final Long outputContentSize) { + this.outputContentSize = outputContentSize; + } + + public Long getBytesRead() { + return bytesRead; + } + + public void setBytesRead(final Long bytesRead) { + this.bytesRead = bytesRead; + } + + public Long getBytesWritten() { + return bytesWritten; + } + + public void setBytesWritten(final Long bytesWritten) { + this.bytesWritten = bytesWritten; + } + + public Integer getQueuedCount() { + return this.queuedCount; + } + + public void setQueuedCount(final Integer queuedCount) { + this.queuedCount = queuedCount; + } + + public Long getQueuedContentSize() { + return queuedContentSize; + } + + public void setQueuedContentSize(final Long queuedContentSize) { + this.queuedContentSize = queuedContentSize; + } + + public Integer getActiveThreadCount() { + return activeThreadCount; + } + + public void setActiveThreadCount(final Integer activeThreadCount) { + this.activeThreadCount = activeThreadCount; + } + + public Integer getStatelessActiveThreadCount() { + return statelessActiveThreadCount; + } + + public void setStatelessActiveThreadCount(final Integer statelessActiveThreadCount) { + this.statelessActiveThreadCount = statelessActiveThreadCount; + } + + public Integer getTerminatedThreadCount() { + return terminatedThreadCount; + } + + public void setTerminatedThreadCount(Integer terminatedThreadCount) { + this.terminatedThreadCount = terminatedThreadCount; + } + + public Collection<ConnectionStatus> getConnectionStatus() { + return connectionStatus; + } + + public void setConnectionStatus(final Collection<ConnectionStatus> connectionStatus) { + this.connectionStatus = connectionStatus; + } + + public Collection<ProcessorStatus> getProcessorStatus() { + return processorStatus; + } + + public void setProcessorStatus(final Collection<ProcessorStatus> processorStatus) { + this.processorStatus = processorStatus; + } + + public Collection<ProcessGroupStatus> getProcessGroupStatus() { + return processGroupStatus; + } + + public void setProcessGroupStatus(final Collection<ProcessGroupStatus> processGroupStatus) { + this.processGroupStatus = processGroupStatus; + } + + public Collection<PortStatus> getInputPortStatus() { + return inputPortStatus; + } + + public void setInputPortStatus(Collection<PortStatus> inputPortStatus) { + this.inputPortStatus = inputPortStatus; + } + + public Collection<PortStatus> getOutputPortStatus() { + return outputPortStatus; + } + + public void setOutputPortStatus(Collection<PortStatus> outputPortStatus) { + this.outputPortStatus = outputPortStatus; + } + + public Collection<RemoteProcessGroupStatus> getRemoteProcessGroupStatus() { + return remoteProcessGroupStatus; + } + + public void setRemoteProcessGroupStatus(final Collection<RemoteProcessGroupStatus> remoteProcessGroupStatus) { + this.remoteProcessGroupStatus = remoteProcessGroupStatus; + } + + public int getFlowFilesReceived() { + return flowFilesReceived; + } + + public void setFlowFilesReceived(final int flowFilesReceived) { + this.flowFilesReceived = flowFilesReceived; + } + + public long getBytesReceived() { + return bytesReceived; + } + + public void setBytesReceived(final long bytesReceived) { + this.bytesReceived = bytesReceived; + } + + public int getFlowFilesSent() { + return flowFilesSent; + } + + public void setFlowFilesSent(final int flowFilesSent) { + this.flowFilesSent = flowFilesSent; + } + + public long getBytesSent() { + return bytesSent; + } + + public void setBytesSent(final long bytesSent) { + this.bytesSent = bytesSent; + } + + public int getFlowFilesTransferred() { + return flowFilesTransferred; + } + + public void setFlowFilesTransferred(int flowFilesTransferred) { + this.flowFilesTransferred = flowFilesTransferred; + } + + public long getBytesTransferred() { + return bytesTransferred; + } + + public void setBytesTransferred(long bytesTransferred) { + this.bytesTransferred = bytesTransferred; + } + + public long getProcessingNanos() { + return processingNanos; + } + + public void setProcessingNanos(long processingNanos) { + this.processingNanos = processingNanos; + } + + public ProcessingPerformanceStatus getProcessingPerformanceStatus() { + return processingPerformanceStatus; + } + + public void setProcessingPerformanceStatus(ProcessingPerformanceStatus processingPerformanceStatus) { + this.processingPerformanceStatus = processingPerformanceStatus; + } + + @Override + public ProcessGroupStatus clone() { + final ProcessGroupStatus clonedObj = new ProcessGroupStatus(); + + clonedObj.id = id; + clonedObj.name = name; + clonedObj.outputContentSize = outputContentSize; + clonedObj.outputCount = outputCount; + clonedObj.inputContentSize = inputContentSize; + clonedObj.inputCount = inputCount; + clonedObj.activeThreadCount = activeThreadCount; + clonedObj.terminatedThreadCount = terminatedThreadCount; + clonedObj.queuedContentSize = queuedContentSize; + clonedObj.queuedCount = queuedCount; + clonedObj.bytesRead = bytesRead; + clonedObj.bytesWritten = bytesWritten; + clonedObj.flowFilesReceived = flowFilesReceived; + clonedObj.bytesReceived = bytesReceived; + clonedObj.flowFilesSent = flowFilesSent; + clonedObj.bytesSent = bytesSent; + clonedObj.flowFilesTransferred = flowFilesTransferred; + clonedObj.bytesTransferred = bytesTransferred; + clonedObj.processingNanos = processingNanos; + clonedObj.processingPerformanceStatus = processingPerformanceStatus; + + if (connectionStatus != null) { + final Collection<ConnectionStatus> statusList = new ArrayList<>(); + clonedObj.setConnectionStatus(statusList); + for (final ConnectionStatus status : connectionStatus) { + statusList.add(status.clone()); + } + } + + if (processorStatus != null) { + final Collection<ProcessorStatus> statusList = new ArrayList<>(); + clonedObj.setProcessorStatus(statusList); + for (final ProcessorStatus status : processorStatus) { + statusList.add(status.clone()); + } + } + + if (inputPortStatus != null) { + final Collection<PortStatus> statusList = new ArrayList<>(); + clonedObj.setInputPortStatus(statusList); + for (final PortStatus status : inputPortStatus) { + statusList.add(status.clone()); + } + } + + if (outputPortStatus != null) { + final Collection<PortStatus> statusList = new ArrayList<>(); + clonedObj.setOutputPortStatus(statusList); + for (final PortStatus status : outputPortStatus) { + statusList.add(status.clone()); + } + } + + if (processGroupStatus != null) { + final Collection<ProcessGroupStatus> statusList = new ArrayList<>(); + clonedObj.setProcessGroupStatus(statusList); + for (final ProcessGroupStatus status : processGroupStatus) { + statusList.add(status.clone()); + } + } + + if (remoteProcessGroupStatus != null) { + final Collection<RemoteProcessGroupStatus> statusList = new ArrayList<>(); + clonedObj.setRemoteProcessGroupStatus(statusList); + for (final RemoteProcessGroupStatus status : remoteProcessGroupStatus) { + statusList.add(status.clone()); + } + } + + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ProcessGroupStatus [id="); + builder.append(id); + builder.append(", inputCount="); + builder.append(inputCount); + builder.append(", inputBytes="); + builder.append(inputContentSize); + builder.append(", outputCount="); + builder.append(outputCount); + builder.append(", outputBytes="); + builder.append(outputContentSize); + builder.append(", activeThreadCount="); + builder.append(activeThreadCount); + builder.append(", terminatedThreadCount="); + builder.append(terminatedThreadCount); + builder.append(", flowFilesTransferred="); + builder.append(flowFilesTransferred); + builder.append(", bytesTransferred="); + builder.append(bytesTransferred); + builder.append(", flowFilesReceived="); + builder.append(flowFilesReceived); + builder.append(", bytesReceived="); + builder.append(bytesReceived); + builder.append(", flowFilesSent="); + builder.append(flowFilesSent); + builder.append(", bytesSent="); + builder.append(bytesSent); + builder.append(", processingNanos="); + builder.append(processingNanos); + builder.append(",\n\tconnectionStatus="); + + for (final ConnectionStatus status : connectionStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(",\n\tprocessorStatus="); + + for (final ProcessorStatus status : processorStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(",\n\tprocessGroupStatus="); + + for (final ProcessGroupStatus status : processGroupStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(",\n\tremoteProcessGroupStatus="); + for (final RemoteProcessGroupStatus status : remoteProcessGroupStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(",\n\tinputPortStatus="); + for (final PortStatus status : inputPortStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(",\n\toutputPortStatus="); + for (final PortStatus status : outputPortStatus) { + builder.append("\n\t\t"); + builder.append(status); + } + + builder.append(", processingPerformanceStatus="); + builder.append(processingPerformanceStatus); + + builder.append("]"); + return builder.toString(); + } + + public static void merge(final ProcessGroupStatus target, final ProcessGroupStatus toMerge) { + if (target == null || toMerge == null) { + return; + } + + target.setInputCount(target.getInputCount() + toMerge.getInputCount()); + target.setInputContentSize(target.getInputContentSize() + toMerge.getInputContentSize()); + target.setOutputCount(target.getOutputCount() + toMerge.getOutputCount()); + target.setOutputContentSize(target.getOutputContentSize() + toMerge.getOutputContentSize()); + target.setQueuedCount(target.getQueuedCount() + toMerge.getQueuedCount()); + target.setQueuedContentSize(target.getQueuedContentSize() + toMerge.getQueuedContentSize()); + target.setBytesRead(target.getBytesRead() + toMerge.getBytesRead()); + target.setBytesWritten(target.getBytesWritten() + toMerge.getBytesWritten()); + target.setActiveThreadCount(target.getActiveThreadCount() + toMerge.getActiveThreadCount()); + target.setStatelessActiveThreadCount(target.getStatelessActiveThreadCount() + toMerge.getStatelessActiveThreadCount()); + target.setTerminatedThreadCount(target.getTerminatedThreadCount() + toMerge.getTerminatedThreadCount()); + target.setFlowFilesTransferred(target.getFlowFilesTransferred() + toMerge.getFlowFilesTransferred()); + target.setBytesTransferred(target.getBytesTransferred() + toMerge.getBytesTransferred()); + target.setFlowFilesReceived(target.getFlowFilesReceived() + toMerge.getFlowFilesReceived()); + target.setBytesReceived(target.getBytesReceived() + toMerge.getBytesReceived()); + target.setFlowFilesSent(target.getFlowFilesSent() + toMerge.getFlowFilesSent()); + target.setBytesSent(target.getBytesSent() + toMerge.getBytesSent()); + target.setProcessingNanos(target.getProcessingNanos() + toMerge.getProcessingNanos()); + + // if the versioned flow state to merge is sync failure allow it to take precedence. + if (VersionedFlowState.SYNC_FAILURE.equals(toMerge.getVersionedFlowState())) { + target.setVersionedFlowState(VersionedFlowState.SYNC_FAILURE); + } + + // connection status + // sort by id + final Map<String, ConnectionStatus> mergedConnectionMap = new HashMap<>(); + for (final ConnectionStatus status : target.getConnectionStatus()) { + mergedConnectionMap.put(status.getId(), status); + } + + for (final ConnectionStatus statusToMerge : toMerge.getConnectionStatus()) { + ConnectionStatus merged = mergedConnectionMap.get(statusToMerge.getId()); + if (merged == null) { + mergedConnectionMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + merged.setQueuedCount(merged.getQueuedCount() + statusToMerge.getQueuedCount()); + merged.setQueuedBytes(merged.getQueuedBytes() + statusToMerge.getQueuedBytes()); + merged.setInputCount(merged.getInputCount() + statusToMerge.getInputCount()); + merged.setInputBytes(merged.getInputBytes() + statusToMerge.getInputBytes()); + merged.setOutputCount(merged.getOutputCount() + statusToMerge.getOutputCount()); + merged.setOutputBytes(merged.getOutputBytes() + statusToMerge.getOutputBytes()); + merged.setFlowFileAvailability(mergeFlowFileAvailability(merged.getFlowFileAvailability(), statusToMerge.getFlowFileAvailability())); + merged.setLoadBalanceStatus(mergeLoadBalanceStatus(merged.getLoadBalanceStatus(), statusToMerge.getLoadBalanceStatus())); + } + target.setConnectionStatus(mergedConnectionMap.values()); + + // processor status + final Map<String, ProcessorStatus> mergedProcessorMap = new HashMap<>(); + for (final ProcessorStatus status : target.getProcessorStatus()) { + mergedProcessorMap.put(status.getId(), status); + } + + for (final ProcessorStatus statusToMerge : toMerge.getProcessorStatus()) { + ProcessorStatus merged = mergedProcessorMap.get(statusToMerge.getId()); + if (merged == null) { + mergedProcessorMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + merged.setActiveThreadCount(merged.getActiveThreadCount() + statusToMerge.getActiveThreadCount()); + merged.setTerminatedThreadCount(merged.getTerminatedThreadCount() + statusToMerge.getTerminatedThreadCount()); + merged.setBytesRead(merged.getBytesRead() + statusToMerge.getBytesRead()); + merged.setBytesWritten(merged.getBytesWritten() + statusToMerge.getBytesWritten()); + merged.setInputBytes(merged.getInputBytes() + statusToMerge.getInputBytes()); + merged.setInputCount(merged.getInputCount() + statusToMerge.getInputCount()); + merged.setInvocations(merged.getInvocations() + statusToMerge.getInvocations()); + merged.setOutputBytes(merged.getOutputBytes() + statusToMerge.getOutputBytes()); + merged.setOutputCount(merged.getOutputCount() + statusToMerge.getOutputCount()); + merged.setProcessingNanos(merged.getProcessingNanos() + statusToMerge.getProcessingNanos()); + merged.setFlowFilesRemoved(merged.getFlowFilesRemoved() + statusToMerge.getFlowFilesRemoved()); + + // if the status to merge is invalid allow it to take precedence. whether the + // processor run status is disabled/stopped/running is part of the flow configuration + // and should not differ amongst nodes. however, whether a processor is invalid + // can be driven by environmental conditions. this check allows any of those to + // take precedence over the configured run status. + if (RunStatus.Validating.equals(statusToMerge.getRunStatus())) { + merged.setRunStatus(RunStatus.Validating); + } else if (RunStatus.Invalid.equals(statusToMerge.getRunStatus())) { + merged.setRunStatus(RunStatus.Invalid); + } + } + target.setProcessorStatus(mergedProcessorMap.values()); + + // input ports + final Map<String, PortStatus> mergedInputPortMap = new HashMap<>(); + for (final PortStatus status : target.getInputPortStatus()) { + mergedInputPortMap.put(status.getId(), status); + } + + for (final PortStatus statusToMerge : toMerge.getInputPortStatus()) { + PortStatus merged = mergedInputPortMap.get(statusToMerge.getId()); + if (merged == null) { + mergedInputPortMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + merged.setInputBytes(merged.getInputBytes() + statusToMerge.getInputBytes()); + merged.setInputCount(merged.getInputCount() + statusToMerge.getInputCount()); + merged.setOutputBytes(merged.getOutputBytes() + statusToMerge.getOutputBytes()); + merged.setOutputCount(merged.getOutputCount() + statusToMerge.getOutputCount()); + merged.setActiveThreadCount(merged.getActiveThreadCount() + statusToMerge.getActiveThreadCount()); + if (statusToMerge.isTransmitting() != null && statusToMerge.isTransmitting()) { + merged.setTransmitting(true); + } + + // should be unnecessary here since ports run status should not be affected by + // environmental conditions but doing so in case that changes + if (RunStatus.Invalid.equals(statusToMerge.getRunStatus())) { + merged.setRunStatus(RunStatus.Invalid); + } + } + target.setInputPortStatus(mergedInputPortMap.values()); + + // output ports + final Map<String, PortStatus> mergedOutputPortMap = new HashMap<>(); + for (final PortStatus status : target.getOutputPortStatus()) { + mergedOutputPortMap.put(status.getId(), status); + } + + for (final PortStatus statusToMerge : toMerge.getOutputPortStatus()) { + PortStatus merged = mergedOutputPortMap.get(statusToMerge.getId()); + if (merged == null) { + mergedOutputPortMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + merged.setInputBytes(merged.getInputBytes() + statusToMerge.getInputBytes()); + merged.setInputCount(merged.getInputCount() + statusToMerge.getInputCount()); + merged.setOutputBytes(merged.getOutputBytes() + statusToMerge.getOutputBytes()); + merged.setOutputCount(merged.getOutputCount() + statusToMerge.getOutputCount()); + merged.setActiveThreadCount(merged.getActiveThreadCount() + statusToMerge.getActiveThreadCount()); + if (statusToMerge.isTransmitting() != null && statusToMerge.isTransmitting()) { + merged.setTransmitting(true); + } + + // should be unnecessary here since ports run status not should be affected by + // environmental conditions but doing so in case that changes + if (RunStatus.Invalid.equals(statusToMerge.getRunStatus())) { + merged.setRunStatus(RunStatus.Invalid); + } + } + target.setOutputPortStatus(mergedOutputPortMap.values()); + + // child groups + final Map<String, ProcessGroupStatus> mergedGroupMap = new HashMap<>(); + for (final ProcessGroupStatus status : target.getProcessGroupStatus()) { + mergedGroupMap.put(status.getId(), status); + } + + for (final ProcessGroupStatus statusToMerge : toMerge.getProcessGroupStatus()) { + ProcessGroupStatus merged = mergedGroupMap.get(statusToMerge.getId()); + if (merged == null) { + mergedGroupMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + merge(merged, statusToMerge); + } + target.setOutputPortStatus(mergedOutputPortMap.values()); + + // remote groups + final Map<String, RemoteProcessGroupStatus> mergedRemoteGroupMap = new HashMap<>(); + for (final RemoteProcessGroupStatus status : target.getRemoteProcessGroupStatus()) { + mergedRemoteGroupMap.put(status.getId(), status); + } + + for (final RemoteProcessGroupStatus statusToMerge : toMerge.getRemoteProcessGroupStatus()) { + RemoteProcessGroupStatus merged = mergedRemoteGroupMap.get(statusToMerge.getId()); + if (merged == null) { + mergedRemoteGroupMap.put(statusToMerge.getId(), statusToMerge.clone()); + continue; + } + + // NOTE - active/inactive port counts are not merged since that state is considered part of the flow (like runStatus) + merged.setReceivedContentSize(merged.getReceivedContentSize() + statusToMerge.getReceivedContentSize()); + merged.setReceivedCount(merged.getReceivedCount() + statusToMerge.getReceivedCount()); + merged.setSentContentSize(merged.getSentContentSize() + statusToMerge.getSentContentSize()); + merged.setSentCount(merged.getSentCount() + statusToMerge.getSentCount()); + merged.setActiveThreadCount(merged.getActiveThreadCount() + statusToMerge.getActiveThreadCount()); + + // Take the earliest last refresh time + final Date mergedLastRefreshTime = merged.getLastRefreshTime(); + final Date toMergeLastRefreshTime = statusToMerge.getLastRefreshTime(); + if (mergedLastRefreshTime == null || (toMergeLastRefreshTime != null && toMergeLastRefreshTime.before(mergedLastRefreshTime))) { + merged.setLastRefreshTime(toMergeLastRefreshTime); + } + } + + target.setRemoteProcessGroupStatus(mergedRemoteGroupMap.values()); + + final ProcessingPerformanceStatus targetPerformanceStatus = target.getProcessingPerformanceStatus(); + final ProcessingPerformanceStatus toMergePerformanceStatus = toMerge.getProcessingPerformanceStatus(); + + if (targetPerformanceStatus != null && toMergePerformanceStatus != null) { + targetPerformanceStatus.setIdentifier(toMergePerformanceStatus.getIdentifier()); + targetPerformanceStatus.setCpuDuration(targetPerformanceStatus.getCpuDuration() + toMergePerformanceStatus.getCpuDuration()); + targetPerformanceStatus.setContentReadDuration(targetPerformanceStatus.getContentReadDuration() + toMergePerformanceStatus.getContentReadDuration()); + targetPerformanceStatus.setContentWriteDuration(targetPerformanceStatus.getContentWriteDuration() + toMergePerformanceStatus.getContentWriteDuration()); + targetPerformanceStatus.setSessionCommitDuration(targetPerformanceStatus.getSessionCommitDuration() + toMergePerformanceStatus.getSessionCommitDuration()); + targetPerformanceStatus.setGarbageCollectionDuration(targetPerformanceStatus.getGarbageCollectionDuration() + toMergePerformanceStatus.getGarbageCollectionDuration()); + } else { + target.setProcessingPerformanceStatus(targetPerformanceStatus); + } + } + + public static FlowFileAvailability mergeFlowFileAvailability(final FlowFileAvailability availabilityA, final FlowFileAvailability availabilityB) { + if (availabilityA == availabilityB) { + return availabilityA; + } + if (availabilityA == null) { + return availabilityB; + } + if (availabilityB == null) { + return availabilityA; + } + + if (availabilityA == FlowFileAvailability.FLOWFILE_AVAILABLE || availabilityB == FlowFileAvailability.FLOWFILE_AVAILABLE) { + return FlowFileAvailability.FLOWFILE_AVAILABLE; + } + + if (availabilityA == FlowFileAvailability.HEAD_OF_QUEUE_PENALIZED || availabilityB == FlowFileAvailability.HEAD_OF_QUEUE_PENALIZED) { + return FlowFileAvailability.HEAD_OF_QUEUE_PENALIZED; + } + + return FlowFileAvailability.FLOWFILE_AVAILABLE; + } + + public static LoadBalanceStatus mergeLoadBalanceStatus(final LoadBalanceStatus statusA, final LoadBalanceStatus statusB) { + if (statusA == statusB) { + return statusA; + } + if (statusA == null) { + return statusB; + } + if (statusB == null) { + return statusA; + } + + if (statusA == LoadBalanceStatus.LOAD_BALANCE_ACTIVE || statusB == LoadBalanceStatus.LOAD_BALANCE_ACTIVE) { + return LoadBalanceStatus.LOAD_BALANCE_ACTIVE; + } + + if (statusA == LoadBalanceStatus.LOAD_BALANCE_INACTIVE || statusB == LoadBalanceStatus.LOAD_BALANCE_INACTIVE) { + return LoadBalanceStatus.LOAD_BALANCE_INACTIVE; + } + + return LoadBalanceStatus.LOAD_BALANCE_NOT_CONFIGURED; + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/ProcessingPerformanceStatus.java b/src/main/java/org/apache/nifi/controller/status/ProcessingPerformanceStatus.java new file mode 100644 index 0000000..e87894c --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/ProcessingPerformanceStatus.java
@@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +public class ProcessingPerformanceStatus implements Cloneable { + + private String identifier; + private long cpuDuration; + private long contentReadDuration; + private long contentWriteDuration; + private long sessionCommitDuration; + private long garbageCollectionDuration; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public long getCpuDuration() { + return cpuDuration; + } + + public void setCpuDuration(long cpuDuration) { + this.cpuDuration = cpuDuration; + } + + public long getContentReadDuration() { + return contentReadDuration; + } + + public void setContentReadDuration(long contentReadDuration) { + this.contentReadDuration = contentReadDuration; + } + + public long getContentWriteDuration() { + return contentWriteDuration; + } + + public void setContentWriteDuration(long contentWriteDuration) { + this.contentWriteDuration = contentWriteDuration; + } + + public long getSessionCommitDuration() { + return sessionCommitDuration; + } + + public void setSessionCommitDuration(long sessionCommitDuration) { + this.sessionCommitDuration = sessionCommitDuration; + } + + public long getGarbageCollectionDuration() { + return garbageCollectionDuration; + } + + public void setGarbageCollectionDuration(long garbageCollectionDuration) { + this.garbageCollectionDuration = garbageCollectionDuration; + } + + @Override + public ProcessingPerformanceStatus clone() { + final ProcessingPerformanceStatus clonedObj = new ProcessingPerformanceStatus(); + + clonedObj.identifier = identifier; + clonedObj.cpuDuration = cpuDuration; + clonedObj.contentReadDuration = contentReadDuration; + clonedObj.contentWriteDuration = contentWriteDuration; + clonedObj.sessionCommitDuration = sessionCommitDuration; + clonedObj.garbageCollectionDuration = garbageCollectionDuration; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ProcessorPerformanceStatus [Group ID= "); + builder.append(identifier); + builder.append(", cpuDuration= "); + builder.append(cpuDuration); + builder.append(", contentReadDuration= "); + builder.append(contentReadDuration); + builder.append(", contentWriteDuration= "); + builder.append(contentWriteDuration); + builder.append(", sessionCommitDuration= "); + builder.append(sessionCommitDuration); + builder.append(", garbageCollectionDuration= "); + builder.append(garbageCollectionDuration); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/ProcessorStatus.java b/src/main/java/org/apache/nifi/controller/status/ProcessorStatus.java new file mode 100644 index 0000000..24f74fe --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/ProcessorStatus.java
@@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.nifi.scheduling.ExecutionNode; + +public class ProcessorStatus implements Cloneable { + + private String id; + private String groupId; + private String name; + private String type; + private RunStatus runStatus; + private ExecutionNode executionNode; + private int inputCount; + private long inputBytes; + private int outputCount; + private long outputBytes; + private long bytesRead; + private long bytesWritten; + private int invocations; + private long processingNanos; + private int flowFilesRemoved; + private long averageLineageDuration; + private int activeThreadCount; + private int terminatedThreadCount; + private int flowFilesReceived; + private long bytesReceived; + private int flowFilesSent; + private long bytesSent; + private Map<String, Long> counters; + private ProcessingPerformanceStatus processingPerformanceStatus; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(final String groupId) { + this.groupId = groupId; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public int getInputCount() { + return inputCount; + } + + public RunStatus getRunStatus() { + return runStatus; + } + + public void setRunStatus(RunStatus runStatus) { + this.runStatus = runStatus; + } + + public ExecutionNode getExecutionNode() { + return executionNode; + } + + public void setExecutionNode(ExecutionNode executionNode) { + this.executionNode = executionNode; + } + + public void setInputCount(final int inputCount) { + this.inputCount = inputCount; + } + + public long getInputBytes() { + return inputBytes; + } + + public void setInputBytes(final long inputBytes) { + this.inputBytes = inputBytes; + } + + public int getOutputCount() { + return outputCount; + } + + public void setOutputCount(final int outputCount) { + this.outputCount = outputCount; + } + + public long getOutputBytes() { + return outputBytes; + } + + public void setOutputBytes(final long outputBytes) { + this.outputBytes = outputBytes; + } + + public long getBytesRead() { + return bytesRead; + } + + public void setBytesRead(final long bytesRead) { + this.bytesRead = bytesRead; + } + + public long getBytesWritten() { + return bytesWritten; + } + + public void setBytesWritten(final long bytesWritten) { + this.bytesWritten = bytesWritten; + } + + public int getInvocations() { + return invocations; + } + + public void setInvocations(final int invocations) { + this.invocations = invocations; + } + + public long getProcessingNanos() { + return processingNanos; + } + + public void setProcessingNanos(final long processingNanos) { + this.processingNanos = processingNanos; + } + + public long getAverageLineageDuration(final TimeUnit timeUnit) { + return TimeUnit.MILLISECONDS.convert(averageLineageDuration, timeUnit); + } + + public void setAverageLineageDuration(final long duration, final TimeUnit timeUnit) { + this.averageLineageDuration = timeUnit.toMillis(duration); + } + + public long getAverageLineageDuration() { + return averageLineageDuration; + } + + public void setAverageLineageDuration(final long millis) { + this.averageLineageDuration = millis; + } + + public int getFlowFilesRemoved() { + return flowFilesRemoved; + } + + public void setFlowFilesRemoved(int flowFilesRemoved) { + this.flowFilesRemoved = flowFilesRemoved; + } + + public int getActiveThreadCount() { + return activeThreadCount; + } + + public void setActiveThreadCount(final int activeThreadCount) { + this.activeThreadCount = activeThreadCount; + } + + public int getTerminatedThreadCount() { + return terminatedThreadCount; + } + + public void setTerminatedThreadCount(int terminatedThreadCount) { + this.terminatedThreadCount = terminatedThreadCount; + } + + public int getFlowFilesReceived() { + return flowFilesReceived; + } + + public void setFlowFilesReceived(int flowFilesReceived) { + this.flowFilesReceived = flowFilesReceived; + } + + public long getBytesReceived() { + return bytesReceived; + } + + public void setBytesReceived(long bytesReceived) { + this.bytesReceived = bytesReceived; + } + + public int getFlowFilesSent() { + return flowFilesSent; + } + + public void setFlowFilesSent(int flowFilesSent) { + this.flowFilesSent = flowFilesSent; + } + + public long getBytesSent() { + return bytesSent; + } + + public void setBytesSent(long bytesSent) { + this.bytesSent = bytesSent; + } + + public Map<String, Long> getCounters() { + return counters; + } + + public void setCounters(final Map<String, Long> counters) { + this.counters = counters; + } + + public ProcessingPerformanceStatus getProcessingPerformanceStatus() { + return processingPerformanceStatus; + } + + public void setProcessingPerformanceStatus(ProcessingPerformanceStatus processingPerformanceStatus) { + this.processingPerformanceStatus = processingPerformanceStatus; + } + + @Override + public ProcessorStatus clone() { + final ProcessorStatus clonedObj = new ProcessorStatus(); + clonedObj.activeThreadCount = activeThreadCount; + clonedObj.terminatedThreadCount = terminatedThreadCount; + clonedObj.bytesRead = bytesRead; + clonedObj.bytesWritten = bytesWritten; + clonedObj.flowFilesReceived = flowFilesReceived; + clonedObj.bytesReceived = bytesReceived; + clonedObj.flowFilesSent = flowFilesSent; + clonedObj.bytesSent = bytesSent; + clonedObj.groupId = groupId; + clonedObj.id = id; + clonedObj.inputBytes = inputBytes; + clonedObj.inputCount = inputCount; + clonedObj.invocations = invocations; + clonedObj.name = name; + clonedObj.outputBytes = outputBytes; + clonedObj.outputCount = outputCount; + clonedObj.processingNanos = processingNanos; + clonedObj.averageLineageDuration = averageLineageDuration; + clonedObj.flowFilesRemoved = flowFilesRemoved; + clonedObj.runStatus = runStatus; + clonedObj.executionNode = executionNode; + clonedObj.type = type; + clonedObj.counters = counters == null ? null : new HashMap<>(counters); + clonedObj.processingPerformanceStatus = processingPerformanceStatus; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ProcessorStatus [id="); + builder.append(id); + builder.append(", groupId="); + builder.append(groupId); + builder.append(", name="); + builder.append(name); + builder.append(", type="); + builder.append(type); + builder.append(", runStatus="); + builder.append(runStatus); + builder.append(", executionNode="); + builder.append(executionNode); + builder.append(", inputCount="); + builder.append(inputCount); + builder.append(", inputBytes="); + builder.append(inputBytes); + builder.append(", outputCount="); + builder.append(outputCount); + builder.append(", outputBytes="); + builder.append(outputBytes); + builder.append(", bytesRead="); + builder.append(bytesRead); + builder.append(", bytesWritten="); + builder.append(bytesWritten); + builder.append(", invocations="); + builder.append(invocations); + builder.append(", processingNanos="); + builder.append(processingNanos); + builder.append(", activeThreadCount="); + builder.append(activeThreadCount); + builder.append(", terminatedThreadCount="); + builder.append(terminatedThreadCount); + builder.append(", counters="); + builder.append(counters); + builder.append(", processingPerformanceStatus="); + builder.append(processingPerformanceStatus); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/RemoteProcessGroupStatus.java b/src/main/java/org/apache/nifi/controller/status/RemoteProcessGroupStatus.java new file mode 100644 index 0000000..b0ea196 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/RemoteProcessGroupStatus.java
@@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class RemoteProcessGroupStatus implements Cloneable { + + private String id; + private String groupId; + private TransmissionStatus transmissionStatus; + private String uri; + private String name; + private String comments; + private String authorizationIssue; + private Date lastRefreshTime; + private Integer activeThreadCount; + private int sentCount; + private long sentContentSize; + private int receivedCount; + private long receivedContentSize; + private Integer activeRemotePortCount; + private Integer inactiveRemotePortCount; + + private long averageLineageDuration; + + public String getTargetUri() { + return uri; + } + + public void setTargetUri(String uri) { + this.uri = uri; + } + + public TransmissionStatus getTransmissionStatus() { + return transmissionStatus; + } + + public void setTransmissionStatus(TransmissionStatus transmissionStatus) { + this.transmissionStatus = transmissionStatus; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getComments() { + return comments; + } + + public void setComments(String comments) { + this.comments = comments; + } + + public String getAuthorizationIssue() { + return authorizationIssue; + } + + public void setAuthorizationIssue(String authorizationIssue) { + this.authorizationIssue = authorizationIssue; + } + + public Date getLastRefreshTime() { + return lastRefreshTime; + } + + public void setLastRefreshTime(Date lastRefreshTime) { + this.lastRefreshTime = lastRefreshTime; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getActiveThreadCount() { + return activeThreadCount; + } + + public void setActiveThreadCount(Integer activeThreadCount) { + this.activeThreadCount = activeThreadCount; + } + + public Integer getSentCount() { + return sentCount; + } + + public void setSentCount(Integer sentCount) { + this.sentCount = sentCount; + } + + public Long getSentContentSize() { + return sentContentSize; + } + + public void setSentContentSize(Long sentContentSize) { + this.sentContentSize = sentContentSize; + } + + public Integer getReceivedCount() { + return receivedCount; + } + + public void setReceivedCount(Integer receivedCount) { + this.receivedCount = receivedCount; + } + + public Long getReceivedContentSize() { + return receivedContentSize; + } + + public void setReceivedContentSize(Long receivedContentSize) { + this.receivedContentSize = receivedContentSize; + } + + public Integer getActiveRemotePortCount() { + return activeRemotePortCount; + } + + public void setActiveRemotePortCount(Integer activeRemotePortCount) { + this.activeRemotePortCount = activeRemotePortCount; + } + + public Integer getInactiveRemotePortCount() { + return inactiveRemotePortCount; + } + + public void setInactiveRemotePortCount(Integer inactiveRemotePortCount) { + this.inactiveRemotePortCount = inactiveRemotePortCount; + } + + public long getAverageLineageDuration() { + return averageLineageDuration; + } + + public void setAverageLineageDuration(final long millis) { + this.averageLineageDuration = millis; + } + + public long getAverageLineageDuration(final TimeUnit timeUnit) { + return TimeUnit.MILLISECONDS.convert(averageLineageDuration, timeUnit); + } + + public void setAverageLineageDuration(final long duration, final TimeUnit timeUnit) { + this.averageLineageDuration = timeUnit.toMillis(duration); + } + + @Override + public RemoteProcessGroupStatus clone() { + final RemoteProcessGroupStatus clonedObj = new RemoteProcessGroupStatus(); + clonedObj.id = id; + clonedObj.groupId = groupId; + clonedObj.name = name; + clonedObj.comments = comments; + clonedObj.authorizationIssue = authorizationIssue; + clonedObj.lastRefreshTime = lastRefreshTime; + clonedObj.uri = uri; + clonedObj.activeThreadCount = activeThreadCount; + clonedObj.transmissionStatus = transmissionStatus; + clonedObj.sentCount = sentCount; + clonedObj.sentContentSize = sentContentSize; + clonedObj.receivedCount = receivedCount; + clonedObj.receivedContentSize = receivedContentSize; + clonedObj.activeRemotePortCount = activeRemotePortCount; + clonedObj.inactiveRemotePortCount = inactiveRemotePortCount; + clonedObj.averageLineageDuration = averageLineageDuration; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RemoteProcessGroupStatus [id="); + builder.append(id); + builder.append(", groupId="); + builder.append(groupId); + builder.append(", name="); + builder.append(name); + builder.append(", comments="); + builder.append(comments); + builder.append(", authorizationIssue="); + builder.append(authorizationIssue); + builder.append(", lastRefreshTime="); + builder.append(lastRefreshTime); + builder.append(", uri="); + builder.append(uri); + builder.append(", activeThreadCount="); + builder.append(activeThreadCount); + builder.append(", transmissionStatus="); + builder.append(transmissionStatus); + builder.append(", sentCount="); + builder.append(sentCount); + builder.append(", sentContentSize="); + builder.append(sentContentSize); + builder.append(", receivedCount="); + builder.append(receivedCount); + builder.append(", receivedContentSize="); + builder.append(receivedContentSize); + builder.append(", activeRemotePortCount="); + builder.append(activeRemotePortCount); + builder.append(", inactiveRemotePortCount="); + builder.append(inactiveRemotePortCount); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/RunStatus.java b/src/main/java/org/apache/nifi/controller/status/RunStatus.java new file mode 100644 index 0000000..aad749f --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/RunStatus.java
@@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +public enum RunStatus { + + Running, + Stopped, + Validating, + Invalid, + Disabled; +}
diff --git a/src/main/java/org/apache/nifi/controller/status/StorageStatus.java b/src/main/java/org/apache/nifi/controller/status/StorageStatus.java new file mode 100644 index 0000000..2b75789 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/StorageStatus.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +/** + * The status of a storage repository. + */ +public class StorageStatus implements Cloneable { + private String name; + private long freeSpace; + private long usedSpace; + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public long getFreeSpace() { + return freeSpace; + } + + public void setFreeSpace(final long freeSpace) { + this.freeSpace = freeSpace; + } + + public long getUsedSpace() { + return usedSpace; + } + + public void setUsedSpace(final long usedSpace) { + this.usedSpace = usedSpace; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("StorageStatus{"); + builder.append("name='").append(name).append('\''); + builder.append(", freeSpace=").append(freeSpace); + builder.append(", usedSpace=").append(usedSpace); + builder.append('}'); + return builder.toString(); + } + + @Override + public StorageStatus clone() { + final StorageStatus clonedObj = new StorageStatus(); + clonedObj.name = name; + clonedObj.freeSpace = freeSpace; + clonedObj.usedSpace = usedSpace; + return clonedObj; + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/TransmissionStatus.java b/src/main/java/org/apache/nifi/controller/status/TransmissionStatus.java new file mode 100644 index 0000000..6d7eb12 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/TransmissionStatus.java
@@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status; + +public enum TransmissionStatus { + + Transmitting, + NotTransmitting; +}
diff --git a/src/main/java/org/apache/nifi/controller/status/analytics/ConnectionStatusPredictions.java b/src/main/java/org/apache/nifi/controller/status/analytics/ConnectionStatusPredictions.java new file mode 100644 index 0000000..73cfa99 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/analytics/ConnectionStatusPredictions.java
@@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status.analytics; + +public class ConnectionStatusPredictions implements Cloneable { + private long predictionIntervalMillis; + private int nextPredictedQueuedCount; + private long nextPredictedQueuedBytes; + private long predictedTimeToCountBackpressureMillis; + private long predictedTimeToBytesBackpressureMillis; + private int predictedPercentCount = 0; + private int predictedPercentBytes = 0; + + public long getPredictionIntervalMillis() { + return predictionIntervalMillis; + } + + public void setPredictionIntervalMillis(long predictionIntervalMillis) { + this.predictionIntervalMillis = predictionIntervalMillis; + } + + public int getNextPredictedQueuedCount() { + return nextPredictedQueuedCount; + } + + public void setNextPredictedQueuedCount(int nextPredictedQueuedCount) { + this.nextPredictedQueuedCount = nextPredictedQueuedCount; + } + + public long getNextPredictedQueuedBytes() { + return nextPredictedQueuedBytes; + } + + public void setNextPredictedQueuedBytes(long nextPredictedQueuedBytes) { + this.nextPredictedQueuedBytes = nextPredictedQueuedBytes; + } + + public long getPredictedTimeToCountBackpressureMillis() { + return predictedTimeToCountBackpressureMillis; + } + + public void setPredictedTimeToCountBackpressureMillis(long predictedTimeToCountBackpressureMillis) { + this.predictedTimeToCountBackpressureMillis = predictedTimeToCountBackpressureMillis; + } + + public long getPredictedTimeToBytesBackpressureMillis() { + return predictedTimeToBytesBackpressureMillis; + } + + public void setPredictedTimeToBytesBackpressureMillis(long predictedTimeToBytesBackpressureMillis) { + this.predictedTimeToBytesBackpressureMillis = predictedTimeToBytesBackpressureMillis; + } + + public int getPredictedPercentCount() { + return predictedPercentCount; + } + + public void setPredictedPercentCount(int predictedPercentCount) { + this.predictedPercentCount = predictedPercentCount; + } + + public int getPredictedPercentBytes() { + return predictedPercentBytes; + } + + public void setPredictedPercentBytes(int predictedPercentBytes) { + this.predictedPercentBytes = predictedPercentBytes; + } + + @Override + public ConnectionStatusPredictions clone() { + final ConnectionStatusPredictions clonedObj = new ConnectionStatusPredictions(); + clonedObj.nextPredictedQueuedBytes = nextPredictedQueuedBytes; + clonedObj.nextPredictedQueuedCount = nextPredictedQueuedCount; + clonedObj.predictedTimeToBytesBackpressureMillis = predictedTimeToBytesBackpressureMillis; + clonedObj.predictedTimeToCountBackpressureMillis = predictedTimeToCountBackpressureMillis; + clonedObj.predictedPercentCount = predictedPercentCount; + clonedObj.predictedPercentBytes = predictedPercentBytes; + return clonedObj; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ConnectionStatusPredictions [id="); + builder.append(", nextPredictedQueuedBytes="); + builder.append(nextPredictedQueuedBytes); + builder.append(", nextPredictedQueuedCount="); + builder.append(nextPredictedQueuedCount); + builder.append(", predictedTimeToBytesBackpressureMillis="); + builder.append(predictedTimeToBytesBackpressureMillis); + builder.append(", predictedTimeToCountBackpressureMillis="); + builder.append(predictedTimeToCountBackpressureMillis); + builder.append(", predictedPercentCount="); + builder.append(predictedPercentCount); + builder.append(", predictedPercentBytes="); + builder.append(predictedPercentBytes); + builder.append("]"); + return builder.toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/controller/status/analytics/QueryWindow.java b/src/main/java/org/apache/nifi/controller/status/analytics/QueryWindow.java new file mode 100644 index 0000000..798e6cf --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/analytics/QueryWindow.java
@@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status.analytics; + +import java.util.Date; + +/** + * QueryWindow is used to track time intervals for querying for observations + */ +public class QueryWindow { + + private long startTimeMillis; + private long endTimeMillis; + + public QueryWindow(long startTimeMillis, long endTimeMillis) { + this.startTimeMillis = startTimeMillis; + this.endTimeMillis = endTimeMillis; + } + + public long getStartTimeMillis() { + return startTimeMillis; + } + + public void setStartTimeMillis(long startTimeMillis) { + this.startTimeMillis = startTimeMillis; + } + + public long getEndTimeMillis() { + return endTimeMillis; + } + + public void setEndTimeMillis(long endTimeMillis) { + this.endTimeMillis = endTimeMillis; + } + + public Date getStartDateTime() { + return new Date(startTimeMillis); + } + + public Date getEndDateTime() { + return new Date(endTimeMillis); + } + + public long getTimeDifferenceMillis() { + return endTimeMillis - startTimeMillis; + } + +}
diff --git a/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalytics.java b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalytics.java new file mode 100644 index 0000000..63e26e9 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalytics.java
@@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status.analytics; + +import java.util.Map; + +/** + * The StatusAnalytics interface offers methods for accessing predicted and other values for a single component (Connection instance, e.g.). + */ +public interface StatusAnalytics { + + /** + * Get the Query Window used by the analytics instance + * @return queryWindow + */ + QueryWindow getQueryWindow(); + + /** + * Get available predictions where the key (String) in the map is the name of the prediction and value (Long) + * is the value for the prediction + * @return map + */ + Map<String, Long> getPredictions(); + + /** + * Return if analytics object supports online learning + * @return boolean + */ + boolean supportsOnlineLearning(); + + +}
diff --git a/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsEngine.java b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsEngine.java new file mode 100644 index 0000000..57c13e6 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsEngine.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status.analytics; + +public interface StatusAnalyticsEngine { + + /** + * Retrieve status analytics object for given component + * @param componentId identifier for component + * @return componenet specific status analytics object + */ + StatusAnalytics getStatusAnalytics(String componentId); + +}
diff --git a/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsModel.java b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsModel.java new file mode 100644 index 0000000..96ea529 --- /dev/null +++ b/src/main/java/org/apache/nifi/controller/status/analytics/StatusAnalyticsModel.java
@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.controller.status.analytics; + +import java.util.Map; +import java.util.stream.Stream; + +public interface StatusAnalyticsModel { + + /** + * Train model with provided observations (features, labels/targets) + * @param features Stream of feature observation values + * @param labels target observation values + */ + void learn(Stream<Double[]> features, Stream<Double> labels); + + /** + * Return a prediction given observation values + * @param feature feature observation values values + * @return prediction of target/label + */ + Double predict(Double[] feature); + + /** + * Predict a feature given a known target and known predictor values (if multiple predictors are included with model) + * @param predictVariableIndex index of feature that we would like to predict (index should align with order provided in model learn method) + * @param knownVariablesWithIndex a map of known predictor values with their indexes if available + * @param label known target value + * @return prediction for variable + */ + Double predictVariable(Integer predictVariableIndex, Map<Integer, Double> knownVariablesWithIndex, Double label); + + /** + * Indicate if model supports online learning (e.g. can learn new observation samples to create a model) + * @return boolean indicating online learning support + */ + Boolean supportsOnlineLearning(); + + /** + * Returns a map of scores relevant to model (e.g. rSquared, Confidence Intervals, etc.) + * @return Map of score names with values + */ + Map<String, Double> getScores(); + + /** + * Resets a model by clearing observations and other calculations + */ + void clear(); + +}
diff --git a/src/main/java/org/apache/nifi/diagnostics/StorageUsage.java b/src/main/java/org/apache/nifi/diagnostics/StorageUsage.java new file mode 100644 index 0000000..9854765 --- /dev/null +++ b/src/main/java/org/apache/nifi/diagnostics/StorageUsage.java
@@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.diagnostics; + +/** + * Disk usage for a file system directory. + * + */ +public class StorageUsage implements Cloneable { + + private String identifier; + + private long freeSpace; + private long totalSpace; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(final String identifier) { + this.identifier = identifier; + } + + public void setFreeSpace(final long freeSpace) { + this.freeSpace = freeSpace; + } + + public long getFreeSpace() { + return freeSpace; + } + + public long getTotalSpace() { + return totalSpace; + } + + public void setTotalSpace(final long totalSpace) { + this.totalSpace = totalSpace; + } + + public long getUsedSpace() { + return totalSpace - freeSpace; + } + + public int getDiskUtilization() { + return Math.round((float) getUsedSpace() / totalSpace * 100); + } + + @Override + public StorageUsage clone() { + final StorageUsage clonedObj = new StorageUsage(); + clonedObj.identifier = identifier; + clonedObj.freeSpace = freeSpace; + clonedObj.totalSpace = totalSpace; + return clonedObj; + } + +}
diff --git a/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java new file mode 100644 index 0000000..727b097 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java
@@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation; + +import org.apache.nifi.annotation.behavior.DynamicProperties; +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.DynamicRelationship; +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.PrimaryNodeOnly; +import org.apache.nifi.annotation.behavior.ReadsAttribute; +import org.apache.nifi.annotation.behavior.ReadsAttributes; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.SideEffectFree; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.behavior.SupportsBatching; +import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties; +import org.apache.nifi.annotation.behavior.SystemResourceConsideration; +import org.apache.nifi.annotation.behavior.TriggerSerially; +import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable; +import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.behavior.WritesAttributes; +import org.apache.nifi.annotation.configuration.DefaultSchedule; +import org.apache.nifi.annotation.configuration.DefaultSettings; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.DeprecationNotice; +import org.apache.nifi.annotation.documentation.MultiProcessorUseCase; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.documentation.UseCase; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.documentation.init.DocumentationControllerServiceInitializationContext; +import org.apache.nifi.documentation.init.DocumentationFlowAnalysisRuleInitializationContext; +import org.apache.nifi.documentation.init.DocumentationParameterProviderInitializationContext; +import org.apache.nifi.documentation.init.DocumentationProcessorInitializationContext; +import org.apache.nifi.documentation.init.DocumentationReportingInitializationContext; +import org.apache.nifi.flowanalysis.FlowAnalysisRule; +import org.apache.nifi.parameter.ParameterProvider; +import org.apache.nifi.processor.Processor; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.reporting.ReportingTask; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Base class for DocumentationWriter that simplifies iterating over all information for a component, creating a separate method + * for each, to ensure that implementations properly override all methods and therefore properly account for all information about + * a component. + * + * Please note that while this class lives within the nifi-api, it is provided primarily as a means for documentation components within + * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the API is needed in order to extract the relevant information and + * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so would cause a circular dependency). By having this homed within + * the nifi-api, the Maven plugin is able to discover the class dynamically and invoke the one or two methods necessary to create the documentation. + * + * This is a new capability in 1.9.0 in preparation for the Extension Registry and therefore, you should + * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is still evolving and may change in a non-backward-compatible manner or even be + * removed from one incremental release to the next. Use at your own risk! + */ +public abstract class AbstractDocumentationWriter implements ExtensionDocumentationWriter { + + @Override + public void initialize(final ConfigurableComponent component) { + try { + if (component instanceof Processor) { + initialize((Processor) component); + } else if (component instanceof ControllerService) { + initialize((ControllerService) component); + } else if (component instanceof ReportingTask) { + initialize((ReportingTask) component); + } else if (component instanceof FlowAnalysisRule) { + initialize((FlowAnalysisRule) component); + } else if (component instanceof ParameterProvider) { + initialize((ParameterProvider) component); + } + } catch (final InitializationException ie) { + throw new RuntimeException("Failed to initialize " + component, ie); + } + } + + protected void initialize(final Processor processor) { + processor.initialize(new DocumentationProcessorInitializationContext()); + } + + protected void initialize(final ControllerService service) throws InitializationException { + service.initialize(new DocumentationControllerServiceInitializationContext()); + } + + protected void initialize(final ReportingTask reportingTask) throws InitializationException { + reportingTask.initialize(new DocumentationReportingInitializationContext()); + } + + protected void initialize(final FlowAnalysisRule flowAnalysisRule) throws InitializationException { + flowAnalysisRule.initialize(new DocumentationFlowAnalysisRuleInitializationContext()); + } + + protected void initialize(final ParameterProvider parameterProvider) throws InitializationException { + parameterProvider.initialize(new DocumentationParameterProviderInitializationContext()); + } + + @Override + public final void write(final ConfigurableComponent component) throws IOException { + write(component, Collections.emptyList(), Collections.emptyMap()); + } + + @Override + public final void write(final ConfigurableComponent component, final Collection<ServiceAPI> providedServices, Map<String, ServiceAPI> propertyServices) throws IOException { + writeHeader(component); + writeBody(component, propertyServices); + + if (providedServices != null && component instanceof ControllerService) { + writeProvidedServices(providedServices); + } + + writeFooter(component); + } + + protected void writeBody(final ConfigurableComponent component, Map<String, ServiceAPI> propertyServices) throws IOException { + writeExtensionName(component.getClass().getName()); + writeExtensionType(getExtensionType(component)); + writeDeprecationNotice(component.getClass().getAnnotation(DeprecationNotice.class)); + writeDescription(getDescription(component)); + writeTags(getTags(component)); + writeProperties(component.getPropertyDescriptors(), propertyServices); + writeDynamicProperties(getDynamicProperties(component)); + writeSupportsSensitiveDynamicProperties(component.getClass().getAnnotation(SupportsSensitiveDynamicProperties.class)); + + if (component instanceof Processor) { + final Processor processor = (Processor) component; + + writeRelationships(processor.getRelationships()); + writeDynamicRelationship(getDynamicRelationship(processor)); + writeReadsAttributes(getReadsAttributes(processor)); + writeWritesAttributes(getWritesAttributes(processor)); + + writeTriggerSerially(processor.getClass().getAnnotation(TriggerSerially.class)); + writeTriggerWhenEmpty(processor.getClass().getAnnotation(TriggerWhenEmpty.class)); + writeTriggerWhenAnyDestinationAvailable(processor.getClass().getAnnotation(TriggerWhenAnyDestinationAvailable.class)); + writeSupportsBatching(processor.getClass().getAnnotation(SupportsBatching.class)); + writePrimaryNodeOnly(processor.getClass().getAnnotation(PrimaryNodeOnly.class)); + writeSideEffectFree(processor.getClass().getAnnotation(SideEffectFree.class)); + writeDefaultSettings(processor.getClass().getAnnotation(DefaultSettings.class)); + } + + writeStatefulInfo(component.getClass().getAnnotation(Stateful.class)); + writeRestrictedInfo(component.getClass().getAnnotation(Restricted.class)); + writeInputRequirementInfo(getInputRequirement(component)); + writeSystemResourceConsiderationInfo(getSystemResourceConsiderations(component)); + writeUseCases(getUseCases(component)); + writeMultiProcessorUseCases(getMultiProcessorUseCases(component)); + writeSeeAlso(component.getClass().getAnnotation(SeeAlso.class)); + writeDefaultSchedule(component.getClass().getAnnotation(DefaultSchedule.class)); + } + + protected String getDescription(final ConfigurableComponent component) { + final CapabilityDescription capabilityDescription = component.getClass().getAnnotation(CapabilityDescription.class); + if (capabilityDescription == null) { + return null; + } + + return capabilityDescription.value(); + } + + protected List<String> getTags(final ConfigurableComponent component) { + final Tags tags = component.getClass().getAnnotation(Tags.class); + if (tags == null) { + return Collections.emptyList(); + } + + final String[] tagValues = tags.value(); + return tagValues == null ? Collections.emptyList() : Arrays.asList(tagValues); + } + + protected List<DynamicProperty> getDynamicProperties(ConfigurableComponent configurableComponent) { + final List<DynamicProperty> dynamicProperties = new ArrayList<>(); + final DynamicProperties dynProps = configurableComponent.getClass().getAnnotation(DynamicProperties.class); + if (dynProps != null) { + Collections.addAll(dynamicProperties, dynProps.value()); + } + + final DynamicProperty dynProp = configurableComponent.getClass().getAnnotation(DynamicProperty.class); + if (dynProp != null) { + dynamicProperties.add(dynProp); + } + + return dynamicProperties; + } + + + private DynamicRelationship getDynamicRelationship(Processor processor) { + return processor.getClass().getAnnotation(DynamicRelationship.class); + } + + + private List<ReadsAttribute> getReadsAttributes(final Processor processor) { + final List<ReadsAttribute> attributes = new ArrayList<>(); + + final ReadsAttributes readsAttributes = processor.getClass().getAnnotation(ReadsAttributes.class); + if (readsAttributes != null) { + Collections.addAll(attributes, readsAttributes.value()); + } + + final ReadsAttribute readsAttribute = processor.getClass().getAnnotation(ReadsAttribute.class); + if (readsAttribute != null) { + attributes.add(readsAttribute); + } + + return attributes; + } + + + private List<WritesAttribute> getWritesAttributes(Processor processor) { + List<WritesAttribute> attributes = new ArrayList<>(); + + WritesAttributes writesAttributes = processor.getClass().getAnnotation(WritesAttributes.class); + if (writesAttributes != null) { + Collections.addAll(attributes, writesAttributes.value()); + } + + WritesAttribute writeAttribute = processor.getClass().getAnnotation(WritesAttribute.class); + if (writeAttribute != null) { + attributes.add(writeAttribute); + } + + return attributes; + } + + private InputRequirement.Requirement getInputRequirement(final ConfigurableComponent component) { + final InputRequirement annotation = component.getClass().getAnnotation(InputRequirement.class); + return annotation == null ? null : annotation.value(); + } + + private List<SystemResourceConsideration> getSystemResourceConsiderations(final ConfigurableComponent component) { + SystemResourceConsideration[] systemResourceConsiderations = component.getClass().getAnnotationsByType(SystemResourceConsideration.class); + if (systemResourceConsiderations.length == 0) { + return Collections.emptyList(); + } + + return Arrays.asList(systemResourceConsiderations); + } + + private List<UseCase> getUseCases(final ConfigurableComponent component) { + UseCase[] useCases = component.getClass().getAnnotationsByType(UseCase.class); + if (useCases.length == 0) { + return Collections.emptyList(); + } + + return Arrays.asList(useCases); + } + + private List<MultiProcessorUseCase> getMultiProcessorUseCases(final ConfigurableComponent component) { + MultiProcessorUseCase[] useCases = component.getClass().getAnnotationsByType(MultiProcessorUseCase.class); + if (useCases.length == 0) { + return Collections.emptyList(); + } + + return Arrays.asList(useCases); + } + + protected ExtensionType getExtensionType(final ConfigurableComponent component) { + if (component instanceof Processor) { + return ExtensionType.PROCESSOR; + } + if (component instanceof ControllerService) { + return ExtensionType.CONTROLLER_SERVICE; + } + if (component instanceof ReportingTask) { + return ExtensionType.REPORTING_TASK; + } + if (component instanceof FlowAnalysisRule) { + return ExtensionType.FLOW_ANALYSIS_RULE; + } + if (component instanceof ParameterProvider) { + return ExtensionType.PARAMETER_PROVIDER; + } + throw new AssertionError("Encountered unknown Configurable Component Type for " + component); + } + + + protected abstract void writeHeader(ConfigurableComponent component) throws IOException; + + protected abstract void writeExtensionName(String extensionName) throws IOException; + + protected abstract void writeExtensionType(ExtensionType extensionType) throws IOException; + + protected abstract void writeDeprecationNotice(final DeprecationNotice deprecationNotice) throws IOException; + + + protected abstract void writeDescription(String description) throws IOException; + + protected abstract void writeTags(List<String> tags) throws IOException; + + protected abstract void writeProperties(List<PropertyDescriptor> properties, Map<String, ServiceAPI> propertyServices) throws IOException; + + protected abstract void writeDynamicProperties(List<DynamicProperty> dynamicProperties) throws IOException; + + protected abstract void writeStatefulInfo(Stateful stateful) throws IOException; + + protected abstract void writeRestrictedInfo(Restricted restricted) throws IOException; + + protected abstract void writeInputRequirementInfo(InputRequirement.Requirement requirement) throws IOException; + + protected abstract void writeSystemResourceConsiderationInfo(List<SystemResourceConsideration> considerations) throws IOException; + + protected abstract void writeSeeAlso(SeeAlso seeAlso) throws IOException; + + protected abstract void writeUseCases(List<UseCase> useCases) throws IOException; + + protected abstract void writeMultiProcessorUseCases(List<MultiProcessorUseCase> useCases) throws IOException; + + protected abstract void writeDefaultSchedule(DefaultSchedule defaultSchedule) throws IOException; + + // Processor-specific methods + protected abstract void writeRelationships(Set<Relationship> relationships) throws IOException; + + protected abstract void writeDynamicRelationship(DynamicRelationship dynamicRelationship) throws IOException; + + protected abstract void writeReadsAttributes(List<ReadsAttribute> attributes) throws IOException; + + protected abstract void writeWritesAttributes(List<WritesAttribute> attributes) throws IOException; + + protected abstract void writeTriggerSerially(TriggerSerially triggerSerially) throws IOException; + + protected abstract void writeTriggerWhenEmpty(TriggerWhenEmpty triggerWhenEmpty) throws IOException; + + protected abstract void writeTriggerWhenAnyDestinationAvailable(TriggerWhenAnyDestinationAvailable triggerWhenAnyDestinationAvailable) throws IOException; + + protected abstract void writeSupportsBatching(SupportsBatching supportsBatching) throws IOException; + + protected abstract void writeSupportsSensitiveDynamicProperties(SupportsSensitiveDynamicProperties supportsSensitiveDynamicProperties) throws IOException; + + protected abstract void writePrimaryNodeOnly(PrimaryNodeOnly primaryNodeOnly) throws IOException; + + protected abstract void writeSideEffectFree(SideEffectFree sideEffectFree) throws IOException; + + protected abstract void writeDefaultSettings(DefaultSettings defaultSettings) throws IOException; + + // ControllerService-specific methods + protected abstract void writeProvidedServices(Collection<ServiceAPI> providedServices) throws IOException; + + protected abstract void writeFooter(ConfigurableComponent component) throws IOException; + +}
diff --git a/src/main/java/org/apache/nifi/documentation/ExtensionDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/ExtensionDocumentationWriter.java new file mode 100644 index 0000000..3fa9266 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/ExtensionDocumentationWriter.java
@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import org.apache.nifi.components.ConfigurableComponent; + +/** + * Generates documentation for an instance of a ConfigurableComponent. + * + * Please note that while this class lives within the nifi-api, it is provided primarily as a means for documentation components within + * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the API is needed in order to extract the relevant information and + * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so would cause a circular dependency). By having this homed within + * the nifi-api, the Maven plugin is able to discover the class dynamically and invoke the one or two methods necessary to create the documentation. + * + * This is a new capability in 1.9.0 in preparation for the Extension Registry and therefore, you should + * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is still evolving and may change in a non-backward-compatible manner or even be + * removed from one incremental release to the next. Use at your own risk! + */ +public interface ExtensionDocumentationWriter { + + /** + * Calls initialize on the component. Must be called before calling any write methods. + * + * @param component the component to initialize + */ + void initialize(final ConfigurableComponent component); + + /** + * Write the documentation for the given component. + * + * @param component the component to document + * @throws IOException if an error occurs writing the documentation + */ + void write(ConfigurableComponent component) throws IOException; + + /** + * Writes the documentation for the given component. + * + * @param component the component to document + * @param provideServices the service APIs implemented by the component + * @param propertyServiceAPIs the service APIs required by the property descriptors of the component + * @throws IOException if an error occurs writing the documentation + */ + void write(ConfigurableComponent component, Collection<ServiceAPI> provideServices, Map<String, ServiceAPI> propertyServiceAPIs) throws IOException; + +}
diff --git a/src/main/java/org/apache/nifi/documentation/ExtensionType.java b/src/main/java/org/apache/nifi/documentation/ExtensionType.java new file mode 100644 index 0000000..00d7058 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/ExtensionType.java
@@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation; + +public enum ExtensionType { + PROCESSOR, + + CONTROLLER_SERVICE, + + REPORTING_TASK, + + FLOW_ANALYSIS_RULE, + + PARAMETER_PROVIDER; +}
diff --git a/src/main/java/org/apache/nifi/documentation/ServiceAPI.java b/src/main/java/org/apache/nifi/documentation/ServiceAPI.java new file mode 100644 index 0000000..6a4a63b --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/ServiceAPI.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation; + +/** + * Describes a Controller Service API that is provided by some implementation. + * + * Please note that while this class lives within the nifi-api, it is provided primarily as a means for documentation components within + * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the API is needed in order to extract the relevant information and + * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so would cause a circular dependency). By having this homed within + * the nifi-api, the Maven plugin is able to discover the class dynamically and invoke the one or two methods necessary to create the documentation. + * + * This is a new capability in 1.9.0 in preparation for the Extension Registry and therefore, you should + * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is still evolving and may change in a non-backward-compatible manner or even be + * removed from one incremental release to the next. Use at your own risk! + */ +public interface ServiceAPI { + /** + * @return the fully qualified class name of the interface implemented by the Controller Service + */ + String getClassName(); + + /** + * @return the Group ID of the bundle that provides the interface + */ + String getGroupId(); + + /** + * @return the Artifact ID of the bundle that provides the interface + */ + String getArtifactId(); + + /** + * @return the Version of the bundle that provides the interface + */ + String getVersion(); +}
diff --git a/src/main/java/org/apache/nifi/documentation/StandardServiceAPI.java b/src/main/java/org/apache/nifi/documentation/StandardServiceAPI.java new file mode 100644 index 0000000..585f8e4 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/StandardServiceAPI.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation; + +public class StandardServiceAPI implements ServiceAPI { + private final String className; + private final String groupId; + private final String artifactId; + private final String version; + + public StandardServiceAPI(final String className, final String groupId, final String artifactId, final String version) { + this.className = className; + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + } + + @Override + public String getClassName() { + return className; + } + + @Override + public String getGroupId() { + return groupId; + } + + @Override + public String getArtifactId() { + return artifactId; + } + + @Override + public String getVersion() { + return version; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/DocumentationControllerServiceInitializationContext.java b/src/main/java/org/apache/nifi/documentation/init/DocumentationControllerServiceInitializationContext.java new file mode 100644 index 0000000..cb8a0f2 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/DocumentationControllerServiceInitializationContext.java
@@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import java.io.File; +import java.util.UUID; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.controller.ControllerServiceInitializationContext; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; + +public class DocumentationControllerServiceInitializationContext implements ControllerServiceInitializationContext { + private final String id = UUID.randomUUID().toString(); + private final ControllerServiceLookup serviceLookup = new EmptyControllerServiceLookup(); + private final ComponentLog componentLog = new NopComponentLog(); + + @Override + public String getIdentifier() { + return id; + } + + @Override + public ControllerServiceLookup getControllerServiceLookup() { + return serviceLookup; + } + + @Override + public ComponentLog getLogger() { + return componentLog; + } + + @Override + public StateManager getStateManager() { + return new NopStateManager(); + } + + @Override + public NodeTypeProvider getNodeTypeProvider() { + return null; + } + + @Override + public String getKerberosServicePrincipal() { + return null; + } + + @Override + public File getKerberosServiceKeytab() { + return null; + } + + @Override + public File getKerberosConfigurationFile() { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/DocumentationFlowAnalysisRuleInitializationContext.java b/src/main/java/org/apache/nifi/documentation/init/DocumentationFlowAnalysisRuleInitializationContext.java new file mode 100644 index 0000000..9582243 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/DocumentationFlowAnalysisRuleInitializationContext.java
@@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.flowanalysis.FlowAnalysisRuleInitializationContext; +import org.apache.nifi.logging.ComponentLog; + +import java.io.File; +import java.util.UUID; + +public class DocumentationFlowAnalysisRuleInitializationContext implements FlowAnalysisRuleInitializationContext { + private final String id = UUID.randomUUID().toString(); + private final ComponentLog componentLog = new NopComponentLog(); + private final NodeTypeProvider nodeTypeProvider = new StandaloneNodeTypeProvider(); + + @Override + public String getIdentifier() { + return id; + } + + @Override + public ComponentLog getLogger() { + return componentLog; + } + + @Override + public NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + @Override + public String getKerberosServicePrincipal() { + return null; + } + + @Override + public File getKerberosServiceKeytab() { + return null; + } + + @Override + public File getKerberosConfigurationFile() { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/DocumentationParameterProviderInitializationContext.java b/src/main/java/org/apache/nifi/documentation/init/DocumentationParameterProviderInitializationContext.java new file mode 100644 index 0000000..03583fc --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/DocumentationParameterProviderInitializationContext.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.parameter.ParameterProviderInitializationContext; + +import java.io.File; +import java.util.UUID; + +public class DocumentationParameterProviderInitializationContext implements ParameterProviderInitializationContext { + private final String id = UUID.randomUUID().toString(); + private final ComponentLog componentLog = new NopComponentLog(); + private final NodeTypeProvider nodeTypeProvider = new StandaloneNodeTypeProvider(); + private final String name = "name"; + + @Override + public String getIdentifier() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public ComponentLog getLogger() { + return componentLog; + } + + @Override + public NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + @Override + public String getKerberosServicePrincipal() { + return null; + } + + @Override + public File getKerberosServiceKeytab() { + return null; + } + + @Override + public File getKerberosConfigurationFile() { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/DocumentationProcessorInitializationContext.java b/src/main/java/org/apache/nifi/documentation/init/DocumentationProcessorInitializationContext.java new file mode 100644 index 0000000..a48dcb6 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/DocumentationProcessorInitializationContext.java
@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import java.io.File; +import java.util.UUID; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processor.ProcessorInitializationContext; + +public class DocumentationProcessorInitializationContext implements ProcessorInitializationContext { + private final String uuid = UUID.randomUUID().toString(); + private final NodeTypeProvider nodeTypeProvider = new StandaloneNodeTypeProvider(); + + @Override + public String getIdentifier() { + return uuid; + } + + @Override + public ComponentLog getLogger() { + return new NopComponentLog(); + } + + @Override + public ControllerServiceLookup getControllerServiceLookup() { + return new EmptyControllerServiceLookup(); + } + + @Override + public NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + @Override + public String getKerberosServicePrincipal() { + return null; + } + + @Override + public File getKerberosServiceKeytab() { + return null; + } + + @Override + public File getKerberosConfigurationFile() { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/DocumentationReportingInitializationContext.java b/src/main/java/org/apache/nifi/documentation/init/DocumentationReportingInitializationContext.java new file mode 100644 index 0000000..bcf216e --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/DocumentationReportingInitializationContext.java
@@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import java.io.File; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.reporting.ReportingInitializationContext; +import org.apache.nifi.scheduling.SchedulingStrategy; + +public class DocumentationReportingInitializationContext implements ReportingInitializationContext { + private final String id = UUID.randomUUID().toString(); + private final ComponentLog componentLog = new NopComponentLog(); + private final NodeTypeProvider nodeTypeProvider = new StandaloneNodeTypeProvider(); + private final String name = "name"; + + @Override + public String getIdentifier() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public long getSchedulingPeriod(final TimeUnit timeUnit) { + return 0; + } + + @Override + public ControllerServiceLookup getControllerServiceLookup() { + return new EmptyControllerServiceLookup(); + } + + @Override + public String getSchedulingPeriod() { + return "0 sec"; + } + + @Override + public SchedulingStrategy getSchedulingStrategy() { + return SchedulingStrategy.TIMER_DRIVEN; + } + + @Override + public ComponentLog getLogger() { + return componentLog; + } + + @Override + public NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + @Override + public String getKerberosServicePrincipal() { + return null; + } + + @Override + public File getKerberosServiceKeytab() { + return null; + } + + @Override + public File getKerberosConfigurationFile() { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/EmptyControllerServiceLookup.java b/src/main/java/org/apache/nifi/documentation/init/EmptyControllerServiceLookup.java new file mode 100644 index 0000000..5cda2af --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/EmptyControllerServiceLookup.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import java.util.Set; +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.controller.ControllerServiceLookup; + +public class EmptyControllerServiceLookup implements ControllerServiceLookup { + @Override + public ControllerService getControllerService(final String serviceIdentifier) { + return null; + } + + @Override + public boolean isControllerServiceEnabled(final String serviceIdentifier) { + return false; + } + + @Override + public boolean isControllerServiceEnabling(final String serviceIdentifier) { + return false; + } + + @Override + public boolean isControllerServiceEnabled(final ControllerService service) { + return false; + } + + @Override + public Set<String> getControllerServiceIdentifiers(final Class<? extends ControllerService> serviceType) throws IllegalArgumentException { + return null; + } + + @Override + public String getControllerServiceName(final String serviceIdentifier) { + return null; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/NopComponentLog.java b/src/main/java/org/apache/nifi/documentation/init/NopComponentLog.java new file mode 100644 index 0000000..dd30eb4 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/NopComponentLog.java
@@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.logging.LogLevel; +import org.apache.nifi.logging.LogMessage; + +public class NopComponentLog implements ComponentLog { + @Override + public void warn(final String msg, final Throwable t) { + + } + + @Override + public void warn(final String msg, final Object... os) { + + } + + @Override + public void warn(final String msg) { + + } + + @Override + public void warn(LogMessage logMessage) { + + } + + @Override + public void trace(final String msg, final Throwable t) { + + } + + @Override + public void trace(final String msg, final Object... os) { + + } + + @Override + public void trace(final String msg) { + + } + + @Override + public void trace(LogMessage logMessage) { + + } + + @Override + public boolean isWarnEnabled() { + return false; + } + + @Override + public boolean isTraceEnabled() { + return false; + } + + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public boolean isErrorEnabled() { + return false; + } + + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void info(final String msg, final Throwable t) { + + } + + @Override + public void info(final String msg, final Object... os) { + + } + + @Override + public void info(final String msg) { + + } + + @Override + public void info(LogMessage message) { + + } + + @Override + public String getName() { + return null; + } + + @Override + public void error(final String msg, final Throwable t) { + + } + + @Override + public void error(final String msg, final Object... os) { + + } + + @Override + public void error(final String msg) { + + } + + @Override + public void error(LogMessage message) { + + } + + @Override + public void debug(final String msg, final Throwable t) { + + } + + @Override + public void debug(final String msg, final Object... os) { + + } + + @Override + public void debug(final String msg) { + + } + + @Override + public void debug(LogMessage message) { + + } + + @Override + public void log(final LogLevel level, final String msg, final Throwable t) { + + } + + @Override + public void log(final LogLevel level, final String msg, final Object... os) { + + } + + @Override + public void log(final LogLevel level, final String msg) { + + } + + @Override + public void log(LogMessage message) { + + } + +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/NopStateManager.java b/src/main/java/org/apache/nifi/documentation/init/NopStateManager.java new file mode 100644 index 0000000..4b4e21e --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/NopStateManager.java
@@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import java.util.Map; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.components.state.StateMap; + +public class NopStateManager implements StateManager { + @Override + public void setState(final Map<String, String> state, final Scope scope) { + } + + @Override + public StateMap getState(final Scope scope) { + return null; + } + + @Override + public boolean replace(final StateMap oldValue, final Map<String, String> newValue, final Scope scope) { + return false; + } + + @Override + public void clear(final Scope scope) { + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/init/StandaloneNodeTypeProvider.java b/src/main/java/org/apache/nifi/documentation/init/StandaloneNodeTypeProvider.java new file mode 100644 index 0000000..a0c5be6 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/init/StandaloneNodeTypeProvider.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.init; + +import org.apache.nifi.controller.NodeTypeProvider; + +public class StandaloneNodeTypeProvider implements NodeTypeProvider { + @Override + public boolean isClustered() { + return false; + } + + @Override + public boolean isPrimary() { + return false; + } +}
diff --git a/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java new file mode 100644 index 0000000..e0b7a54 --- /dev/null +++ b/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
@@ -0,0 +1,670 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.documentation.xml; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.DynamicRelationship; +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.PrimaryNodeOnly; +import org.apache.nifi.annotation.behavior.ReadsAttribute; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Restriction; +import org.apache.nifi.annotation.behavior.SideEffectFree; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.behavior.SupportsBatching; +import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties; +import org.apache.nifi.annotation.behavior.SystemResourceConsideration; +import org.apache.nifi.annotation.behavior.TriggerSerially; +import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable; +import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.configuration.DefaultSchedule; +import org.apache.nifi.annotation.configuration.DefaultSettings; +import org.apache.nifi.annotation.documentation.DeprecationNotice; +import org.apache.nifi.annotation.documentation.MultiProcessorUseCase; +import org.apache.nifi.annotation.documentation.ProcessorConfiguration; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.UseCase; +import org.apache.nifi.components.AllowableValue; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.components.PropertyDependency; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.RequiredPermission; +import org.apache.nifi.components.resource.ResourceDefinition; +import org.apache.nifi.components.resource.ResourceType; +import org.apache.nifi.documentation.AbstractDocumentationWriter; +import org.apache.nifi.documentation.ExtensionType; +import org.apache.nifi.documentation.ServiceAPI; +import org.apache.nifi.processor.Relationship; + +/** + * XML-based implementation of DocumentationWriter + * + * Please note that while this class lives within the nifi-api, it is provided primarily as a means for documentation components within + * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the API is needed in order to extract the relevant information and + * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so would cause a circular dependency). By having this homed within + * the nifi-api, the Maven plugin is able to discover the class dynamically and invoke the one or two methods necessary to create the documentation. + * + * This is a new capability in 1.9.0 in preparation for the Extension Registry and therefore, you should + * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is still evolving and may change in a non-backward-compatible manner or even be + * removed from one incremental release to the next. Use at your own risk! + */ +public class XmlDocumentationWriter extends AbstractDocumentationWriter { + private final XMLStreamWriter writer; + + public XmlDocumentationWriter(final OutputStream out) throws XMLStreamException { + this.writer = XMLOutputFactory.newInstance().createXMLStreamWriter(out, "UTF-8"); + } + + public XmlDocumentationWriter(final XMLStreamWriter writer) { + this.writer = writer; + } + + @Override + protected void writeHeader(final ConfigurableComponent component) throws IOException { + writeStartElement("extension"); + } + + @Override + protected void writeExtensionName(final String extensionName) throws IOException { + writeTextElement("name", extensionName); + } + + @Override + protected void writeExtensionType(final ExtensionType extensionType) throws IOException { + writeTextElement("type", extensionType.name()); + } + + @Override + protected void writeDeprecationNotice(final DeprecationNotice deprecationNotice) throws IOException { + if (deprecationNotice == null) { + return; + } + + final Class[] classes = deprecationNotice.alternatives(); + final String[] classNames = deprecationNotice.classNames(); + + final Set<String> alternatives = new LinkedHashSet<>(); + if (classes != null) { + for (final Class alternativeClass : classes) { + alternatives.add(alternativeClass.getName()); + } + } + + if (classNames != null) { + Collections.addAll(alternatives, classNames); + } + + writeDeprecationNotice(deprecationNotice.reason(), alternatives); + } + + private void writeDeprecationNotice(final String reason, final Set<String> alternatives) throws IOException { + writeStartElement("deprecationNotice"); + + writeTextElement("reason", reason); + writeTextArray("alternatives", "alternative", alternatives); + + writeEndElement(); + } + + + @Override + protected void writeDescription(final String description) throws IOException { + if (description == null) { + return; + } + writeTextElement("description", description); + } + + @Override + protected void writeTags(final List<String> tags) throws IOException { + if (tags == null) { + return; + } + writeTextArray("tags", "tag", tags); + } + + @Override + protected void writeProperties(final List<PropertyDescriptor> properties, Map<String, ServiceAPI> propertyServices) throws IOException { + if (properties == null || properties.isEmpty()) { + return; + } + + writeStartElement("properties"); + for (final PropertyDescriptor property : properties) { + writeProperty(property, propertyServices); + } + writeEndElement(); + } + + private void writeProperty(final PropertyDescriptor property, Map<String, ServiceAPI> propertyServices) throws IOException { + writeStartElement("property"); + + writeTextElement("name", property.getName()); + writeTextElement("displayName", property.getDisplayName()); + writeTextElement("description", property.getDescription()); + if (property.getDefaultValue() != null) { + writeTextElement("defaultValue", property.getDefaultValue()); + } + + if (property.getControllerServiceDefinition() != null) { + writeStartElement("controllerServiceDefinition"); + + final ServiceAPI serviceAPI = propertyServices.get(property.getName()); + if (serviceAPI != null) { + writeTextElement("className", serviceAPI.getClassName()); + writeTextElement("groupId", serviceAPI.getGroupId()); + writeTextElement("artifactId", serviceAPI.getArtifactId()); + writeTextElement("version", serviceAPI.getVersion()); + } else { + writeTextElement("className", property.getControllerServiceDefinition().getName()); + writeTextElement("groupId", "unknown"); + writeTextElement("artifactId", "unknown"); + writeTextElement("version", "unknown"); + } + + writeEndElement(); + } + + if (property.getAllowableValues() != null && !property.getAllowableValues().isEmpty()) { + writeArray("allowableValues", property.getAllowableValues(), this::writeAllowableValue); + } + + writeBooleanElement("required", property.isRequired()); + writeBooleanElement("sensitive", property.isSensitive()); + writeBooleanElement("expressionLanguageSupported", property.isExpressionLanguageSupported()); + if (property.getExpressionLanguageScope() != null) { + writeTextElement("expressionLanguageScope", property.getExpressionLanguageScope().name()); + } + writeBooleanElement("dynamicallyModifiesClasspath", property.isDynamicClasspathModifier()); + writeBooleanElement("dynamic", property.isDynamic()); + writeResourceDefinition(property.getResourceDefinition()); + writeDependencies(property); + + writeEndElement(); + } + + private void writeResourceDefinition(final ResourceDefinition resourceDefinition) throws IOException { + if (resourceDefinition == null) { + return; + } + + writeStartElement("resourceDefinition"); + writeTextElement("cardinality", resourceDefinition.getCardinality().name()); + writeArray("resourceTypes", resourceDefinition.getResourceTypes(), this::writeResourceType); + writeEndElement(); + } + + private void writeResourceType(final ResourceType resourceType) throws IOException { + writeTextElement("resourceType", resourceType.name()); + } + + private void writeAllowableValue(final AllowableValue allowableValue) throws IOException { + writeStartElement("allowableValue"); + writeTextElement("displayName", allowableValue.getDisplayName()); + writeTextElement("value", allowableValue.getValue()); + writeTextElement("description", allowableValue.getDescription()); + writeEndElement(); + } + + private void writeDependencies(final PropertyDescriptor propertyDescriptor) throws IOException { + final Set<PropertyDependency> dependencies = propertyDescriptor.getDependencies(); + if (dependencies == null || dependencies.isEmpty()) { + return; + } + + writeStartElement("dependencies"); + + for (final PropertyDependency dependency : dependencies) { + writeStartElement("dependency"); + writeTextElement("propertyName", dependency.getPropertyName()); + writeTextElement("propertyDisplayName", dependency.getPropertyDisplayName()); + + final Set<String> dependentValues = dependency.getDependentValues(); + if (dependentValues != null) { + writeStartElement("dependentValues"); + for (final String dependentValue : dependentValues) { + writeTextElement("dependentValue", dependentValue); + } + writeEndElement(); + } + + writeEndElement(); + } + + writeEndElement(); + } + + @Override + protected void writeDynamicProperties(final List<DynamicProperty> dynamicProperties) throws IOException { + if (dynamicProperties == null || dynamicProperties.isEmpty()) { + return; + } + writeArray("dynamicProperties", dynamicProperties, this::writeDynamicProperty); + } + + private void writeDynamicProperty(final DynamicProperty property) throws IOException { + writeStartElement("dynamicProperty"); + + writeTextElement("name", property.name()); + writeTextElement("value", property.value()); + writeTextElement("description", property.description()); + writeTextElement("expressionLanguageScope", property.expressionLanguageScope() == null ? null : property.expressionLanguageScope().name()); + + writeEndElement(); + } + + @Override + protected void writeStatefulInfo(final Stateful stateful) throws IOException { + if (stateful == null) { + return; + } + + writeStartElement("stateful"); + writeTextElement("description", stateful.description()); + writeArray("scopes", Arrays.asList(stateful.scopes()), scope -> writeTextElement("scope", scope.name())); + writeEndElement(); + } + + @Override + protected void writeRestrictedInfo(final Restricted restricted) throws IOException { + if (restricted == null) { + return; + } + + writeStartElement("restricted"); + + if (restricted.value() != null && !restricted.value().isEmpty()) { + writeTextElement("generalRestrictionExplanation", restricted.value()); + } + + final Restriction[] restrictions = restricted.restrictions(); + if (restrictions != null) { + writeArray("restrictions", Arrays.asList(restrictions), this::writeRestriction); + } + + writeEndElement(); + } + + private void writeRestriction(final Restriction restriction) throws IOException { + writeStartElement("restriction"); + + final RequiredPermission permission = restriction.requiredPermission(); + final String label = permission == null ? null : permission.getPermissionLabel(); + writeTextElement("requiredPermission", label); + writeTextElement("explanation", restriction.explanation()); + + writeEndElement(); + } + + @Override + protected void writeInputRequirementInfo(final InputRequirement.Requirement requirement) throws IOException { + if (requirement == null) { + return; + } + writeTextElement("inputRequirement", requirement.name()); + } + + @Override + protected void writeSystemResourceConsiderationInfo(final List<SystemResourceConsideration> considerations) throws IOException { + if (considerations == null || considerations.isEmpty()) { + return; + } + writeArray("systemResourceConsiderations", considerations, this::writeSystemResourceConsideration); + } + + private void writeSystemResourceConsideration(final SystemResourceConsideration consideration) throws IOException { + writeStartElement("systemResourceConsideration"); + + writeTextElement("resource", consideration.resource() == null ? null : consideration.resource().name()); + writeTextElement("description", consideration.description()); + + writeEndElement(); + } + + @Override + protected void writeSeeAlso(final SeeAlso seeAlso) throws IOException { + if (seeAlso == null) { + return; + } + + final Class[] classes = seeAlso.value(); + final String[] classNames = seeAlso.classNames(); + + final Set<String> toSee = new LinkedHashSet<>(); + if (classes != null) { + for (final Class classToSee : classes) { + toSee.add(classToSee.getName()); + } + } + + if (classNames != null) { + Collections.addAll(toSee, classNames); + } + + writeTextArray("seeAlso", "see", toSee); + } + + @Override + protected void writeUseCases(final List<UseCase> useCases) throws IOException { + if (useCases.isEmpty()) { + return; + } + + writeArray("useCases", useCases, this::writeUseCase); + } + + private void writeUseCase(final UseCase useCase) throws IOException { + writeStartElement("useCase"); + + writeTextElement("description", useCase.description()); + writeTextElement("notes", useCase.notes()); + writeTextArray("keywords", "keyword", Arrays.asList(useCase.keywords())); + writeTextElement("inputRequirement", useCase.inputRequirement().name()); + writeTextElement("configuration", useCase.configuration()); + + writeEndElement(); + } + + protected void writeMultiProcessorUseCases(final List<MultiProcessorUseCase> multiProcessorUseCases) throws IOException { + if (multiProcessorUseCases.isEmpty()) { + return; + } + + writeArray("multiProcessorUseCases", multiProcessorUseCases, this::writeMultiProcessorUseCase); + } + + private void writeMultiProcessorUseCase(final MultiProcessorUseCase useCase) throws IOException { + writeStartElement("multiProcessorUseCase"); + + writeTextElement("description", useCase.description()); + writeTextElement("notes", useCase.notes()); + writeTextArray("keywords", "keyword", Arrays.asList(useCase.keywords())); + + writeArray("processorConfigurations", Arrays.asList(useCase.configurations()), this::writeUseCaseComponent); + + writeEndElement(); + } + + private void writeUseCaseComponent(final ProcessorConfiguration processorConfig) throws IOException { + writeStartElement("processorConfiguration"); + + String processorClassName = processorConfig.processorClassName(); + if (processorClassName.isEmpty()) { + processorClassName = processorConfig.processorClass().getName(); + } + + writeTextElement("processorClassName", processorClassName); + writeTextElement("configuration", processorConfig.configuration()); + + writeEndElement(); + } + + + @Override + protected void writeRelationships(final Set<Relationship> relationships) throws IOException { + if (relationships == null || relationships.isEmpty()) { + return; + } + + writeArray("relationships", relationships, rel -> { + writeStartElement("relationship"); + + writeTextElement("name", rel.getName()); + writeTextElement("description", rel.getDescription()); + writeBooleanElement("autoTerminated", rel.isAutoTerminated()); + + writeEndElement(); + }); + } + + @Override + protected void writeDynamicRelationship(final DynamicRelationship dynamicRelationship) throws IOException { + if (dynamicRelationship == null) { + return; + } + + writeStartElement("dynamicRelationship"); + writeTextElement("name", dynamicRelationship.name()); + writeTextElement("description", dynamicRelationship.description()); + writeEndElement(); + } + + @Override + protected void writeReadsAttributes(final List<ReadsAttribute> attributes) throws IOException { + if (attributes == null || attributes.isEmpty()) { + return; + } + writeArray("readsAttributes", attributes, this::writeReadsAttribute); + } + + private void writeReadsAttribute(final ReadsAttribute attribute) throws IOException { + writeStartElement("readsAttribute"); + writeTextElement("name", attribute.attribute()); + writeTextElement("description", attribute.description()); + writeEndElement(); + } + + @Override + protected void writeWritesAttributes(final List<WritesAttribute> attributes) throws IOException { + if (attributes == null) { + return; + } + writeArray("writesAttributes", attributes, this::writeWritesAttribute); + } + + private void writeWritesAttribute(final WritesAttribute attribute) throws IOException { + writeStartElement("writesAttribute"); + writeTextElement("name", attribute.attribute()); + writeTextElement("description", attribute.description()); + writeEndElement(); + } + + @Override + protected void writeTriggerSerially(TriggerSerially triggerSerially) throws IOException { + if (triggerSerially == null) { + return; + } + writeBooleanElement("triggerSerially", true); + } + + @Override + protected void writeTriggerWhenEmpty(TriggerWhenEmpty triggerWhenEmpty) throws IOException { + if (triggerWhenEmpty == null) { + return; + } + writeBooleanElement("triggerWhenEmpty", true); + } + + @Override + protected void writeTriggerWhenAnyDestinationAvailable(TriggerWhenAnyDestinationAvailable triggerWhenAnyDestinationAvailable) throws IOException { + if (triggerWhenAnyDestinationAvailable == null) { + return; + } + writeBooleanElement("triggerWhenAnyDestinationAvailable", true); + } + + @Override + protected void writeSupportsBatching(SupportsBatching supportsBatching) throws IOException { + if (supportsBatching == null) { + return; + } + writeBooleanElement("supportsBatching", true); + } + + @Override + protected void writeSupportsSensitiveDynamicProperties(final SupportsSensitiveDynamicProperties supportsSensitiveDynamicProperties) throws IOException { + if (supportsSensitiveDynamicProperties == null) { + return; + } + writeBooleanElement("supportsSensitiveDynamicProperties", true); + } + + @Override + protected void writePrimaryNodeOnly(PrimaryNodeOnly primaryNodeOnly) throws IOException { + if (primaryNodeOnly == null) { + return; + } + writeBooleanElement("primaryNodeOnly", true); + } + + @Override + protected void writeSideEffectFree(SideEffectFree sideEffectFree) throws IOException { + if (sideEffectFree == null) { + return; + } + writeBooleanElement("sideEffectFree", true); + } + + @Override + protected void writeDefaultSchedule(DefaultSchedule defaultSchedule) throws IOException { + if (defaultSchedule == null) { + return; + } + + writeStartElement("defaultSchedule"); + writeTextElement("strategy", defaultSchedule.strategy().name()); + writeTextElement("period", defaultSchedule.period()); + writeTextElement("concurrentTasks", String.valueOf(defaultSchedule.concurrentTasks())); + writeEndElement(); + } + + @Override + protected void writeDefaultSettings(DefaultSettings defaultSettings) throws IOException { + if (defaultSettings == null) { + return; + } + + writeStartElement("defaultSettings"); + writeTextElement("yieldDuration", defaultSettings.yieldDuration()); + writeTextElement("penaltyDuration", defaultSettings.penaltyDuration()); + writeTextElement("bulletinLevel", defaultSettings.bulletinLevel().name()); + writeEndElement(); + } + + @Override + protected void writeFooter(final ConfigurableComponent component) throws IOException { + writeEndElement(); + } + + @Override + protected void writeProvidedServices(final Collection<ServiceAPI> providedServices) throws IOException { + if (providedServices == null || providedServices.isEmpty()) { + return; + } + writeArray("providedServiceAPIs", providedServices, this::writeProvidedService); + } + + private void writeProvidedService(final ServiceAPI service) throws IOException { + writeStartElement("providedServiceAPI"); + + writeTextElement("className", service.getClassName()); + writeTextElement("groupId", service.getGroupId()); + writeTextElement("artifactId", service.getArtifactId()); + writeTextElement("version", service.getVersion()); + + writeEndElement(); + } + + private <T> void writeArray(final String tagName, final Collection<T> values, final ElementWriter<T> writer) throws IOException { + writeStartElement(tagName); + + if (values != null) { + for (final T value : values) { + writer.write(value); + } + } + + writeEndElement(); + } + + + private void writeTextArray(final String outerTagName, final String elementTagName, final Collection<String> values) throws IOException { + writeTextArray(outerTagName, elementTagName, values, String::toString); + } + + private <T> void writeTextArray(final String outerTagName, final String elementTagName, final Collection<T> values, final Function<T, String> transform) throws IOException { + writeStartElement(outerTagName); + + if (values != null) { + for (final T value : values) { + writeStartElement(elementTagName); + if (value != null) { + writeText(transform.apply(value)); + } + writeEndElement(); + } + } + + writeEndElement(); + } + + private void writeText(final String text) throws IOException { + if (text == null) { + return; + } + + try { + writer.writeCharacters(text); + } catch (XMLStreamException e) { + throw new IOException(e); + } + } + + private void writeStartElement(final String elementName) throws IOException { + try { + writer.writeStartElement(elementName); + } catch (final XMLStreamException e) { + throw new IOException(e); + } + } + + private void writeEndElement() throws IOException { + try { + writer.writeEndElement(); + } catch (final XMLStreamException e) { + throw new IOException(e); + } + } + + private void writeTextElement(final String name, final String text) throws IOException { + writeStartElement(name); + writeText(text); + writeEndElement(); + } + + private void writeBooleanElement(final String name, final boolean value) throws IOException { + writeTextElement(name, String.valueOf(value)); + } + + private interface ElementWriter<T> { + void write(T value) throws IOException; + } +}
diff --git a/src/main/java/org/apache/nifi/expression/AttributeExpression.java b/src/main/java/org/apache/nifi/expression/AttributeExpression.java new file mode 100644 index 0000000..e3b8751 --- /dev/null +++ b/src/main/java/org/apache/nifi/expression/AttributeExpression.java
@@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.expression; + +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.registry.EnvironmentVariables; + +/** + * Defines a type of expression language statement that can be applied + * parametrized by various attributes and properties as specified in each of + * the method calls. AttributeExpression evaluations may be also backed by a + * {@link EnvironmentVariables} used to substitute attributes and variables found in + * the expression for which the registry has a value. + */ +public interface AttributeExpression { + + /** + * @return Evaluates the expression without any additional attributes. + * @throws ProcessException if unable to evaluate + */ + String evaluate() throws ProcessException; + + /** + * Evaluates the expression without additional attributes but enables the + * expression result to be decorated before returning. + * + * @param decorator to execute on the resulting expression value + * @return evaluated value + * @throws ProcessException if failure in evaluation + */ + String evaluate(AttributeValueDecorator decorator) throws ProcessException; + + /** + * Evaluates the expression providing access to additional variables + * including the flow file properties such as file size, identifier, etc.. + * and also all of the flow file attributes. + * + * @param flowFile to evaluate + * @return evaluated value + * @throws ProcessException if failure evaluating + */ + String evaluate(FlowFile flowFile) throws ProcessException; + + /** + * Evaluates the expression providing access to additional variables + * including the flow file properties such as file size, identifier, etc.. + * and also all of the flow file attributes. The resulting value after + * executing any variable substitution and expression evaluation is run + * through the given decorator and returned. + * + * @param flowFile to evaluate + * @param decorator for evaluation + * @return evaluated value + * @throws ProcessException if failed to evaluate + */ + String evaluate(FlowFile flowFile, AttributeValueDecorator decorator) throws ProcessException; + + /** + * @return the type that is returned by the Expression + */ + ResultType getResultType(); + + enum ResultType { + STRING, BOOLEAN, WHOLE_NUMBER, DATE, INSTANT, DECIMAL, NUMBER; + } +}
diff --git a/src/main/java/org/apache/nifi/expression/AttributeValueDecorator.java b/src/main/java/org/apache/nifi/expression/AttributeValueDecorator.java new file mode 100644 index 0000000..b2500dd --- /dev/null +++ b/src/main/java/org/apache/nifi/expression/AttributeValueDecorator.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.expression; + +@FunctionalInterface +public interface AttributeValueDecorator { + + /** + * Decorates the given value + * @param attributeValue to decorate + * @return decorated value + */ + String decorate(String attributeValue); +}
diff --git a/src/main/java/org/apache/nifi/expression/ExpressionLanguageCompiler.java b/src/main/java/org/apache/nifi/expression/ExpressionLanguageCompiler.java new file mode 100644 index 0000000..9383d27 --- /dev/null +++ b/src/main/java/org/apache/nifi/expression/ExpressionLanguageCompiler.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.expression; + +import org.apache.nifi.expression.AttributeExpression.ResultType; + +public interface ExpressionLanguageCompiler { + + /** + * Compiles the given Attribute Expression string into an + * AttributeExpression that can be evaluated + * + * @param expression the Attribute Expression to be compiled + * @return expression that can be evaluated + * @throws IllegalArgumentException if the given expression is not valid + */ + AttributeExpression compile(String expression) throws IllegalArgumentException; + + /** + * Indicates whether or not the given string is a valid Attribute + * Expression. + * + * @param expression to validate + * @return if is value or not + */ + boolean isValidExpression(String expression); + + /** + * Attempts to validate the given expression and returns <code>null</code> + * if the expression is syntactically valid or a String indicating why the + * expression is invalid otherwise. + * + * @param expression to validate + * @param allowSurroundingCharacters if <code>true</code> allows characters + * to surround the Expression, otherwise the expression must be exactly + * equal to a valid Expression. E.g., <code>/${path}</code> is valid if and + * only if <code>allowSurroundingCharacters</code> is true + * + * @return a String indicating the reason that the expression is not + * syntactically correct, or <code>null</code> if the expression is + * syntactically correct + */ + String validateExpression(String expression, boolean allowSurroundingCharacters); + + /** + * Returns the ResultType that will be returned by the given Expression + * + * @param expression the Expression to evaluate + * @return result type for the given expression + * @throws IllegalArgumentException if the given Expression is not a valid + * Expression Language Expression; the message of this Exception will + * indicate the problem if the expression is not syntactically valid. + */ + ResultType getResultType(String expression) throws IllegalArgumentException; +}
diff --git a/src/main/java/org/apache/nifi/expression/ExpressionLanguageScope.java b/src/main/java/org/apache/nifi/expression/ExpressionLanguageScope.java new file mode 100644 index 0000000..db52405 --- /dev/null +++ b/src/main/java/org/apache/nifi/expression/ExpressionLanguageScope.java
@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.expression; + +/** + * Indicates the scope of expression language on a property descriptor. + * + * Scope of the expression language is hierarchical. + * NONE -> ENVIRONMENT -> FLOWFILE_ATTRIBUTES + * + * When scope is set to FlowFiles attributes, variables are evaluated + * against attributes of each incoming flow file. If no matching attribute + * is found, env/syst properties will be checked. + * + * NONE - expression language is not supported + * + * ENVIRONMENT - Environment variables defined at JVM level and system properties. + * + * FLOWFILE_ATTRIBUTES - will check attributes of each individual flow file + * + */ +public enum ExpressionLanguageScope { + + /** + * Expression language is disabled + */ + NONE("Not Supported"), + + /** + * Expression language is evaluated against variables in registry + */ + ENVIRONMENT("Environment variables defined at JVM level and system properties"), + + /** + * Expression language is evaluated per flow file using attributes + */ + FLOWFILE_ATTRIBUTES("Environment variables and FlowFile Attributes"); + + private String description; + + ExpressionLanguageScope(String description) { + this.description = description; + } + + public String getDescription() { + return this.description; + } + +}
diff --git a/src/main/java/org/apache/nifi/flow/BatchSize.java b/src/main/java/org/apache/nifi/flow/BatchSize.java new file mode 100644 index 0000000..05ba824 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/BatchSize.java
@@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Objects; + + +public class BatchSize { + private Integer count; + private String size; + private String duration; + + @Schema(description = "Preferred number of flow files to include in a transaction.") + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + @Schema(description = "Preferred number of bytes to include in a transaction.") + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + @Schema(description = "Preferred amount of time that a transaction should span.") + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + @Override + public int hashCode() { + return Objects.hash(count, duration, size); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final BatchSize other = (BatchSize) obj; + return Objects.equals(count, other.count) && Objects.equals(size, other.size) && Objects.equals(duration, other.duration); + } +}
diff --git a/src/main/java/org/apache/nifi/flow/Bundle.java b/src/main/java/org/apache/nifi/flow/Bundle.java new file mode 100644 index 0000000..ddfd567 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/Bundle.java
@@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + +public class Bundle { + private String group; + private String artifact; + private String version; + + public Bundle() { + } + + public Bundle(final String group, final String artifact, final String version) { + this.group = group; + this.artifact = artifact; + this.version = version; + } + + @Schema(description = "The group of the bundle") + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + @Schema(description = "The artifact of the bundle") + public String getArtifact() { + return artifact; + } + + public void setArtifact(String artifact) { + this.artifact = artifact; + } + + @Schema(description = "The version of the bundle") + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + final Bundle other = (Bundle) obj; + return Objects.equals(group, other.group) && Objects.equals(artifact, other.artifact) && Objects.equals(version, other.version); + } + + @Override + public int hashCode() { + return Objects.hash(group, artifact, version); + } + + @Override + public String toString() { + return "Bundle[" + + "group='" + group + "'" + + ", artifact='" + artifact + "'" + + ", version='" + version + "'" + + "]"; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/ComponentType.java b/src/main/java/org/apache/nifi/flow/ComponentType.java new file mode 100644 index 0000000..23b2ef3 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ComponentType.java
@@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum ComponentType { + + CONNECTION("Connection"), + PROCESSOR("Processor"), + PROCESS_GROUP("Process Group"), + REMOTE_PROCESS_GROUP("Remote Process Group"), + INPUT_PORT("Input Port"), + OUTPUT_PORT("Output Port"), + REMOTE_INPUT_PORT("Remote Input Port"), + REMOTE_OUTPUT_PORT("Remote Output Port"), + FUNNEL("Funnel"), + LABEL("Label"), + CONTROLLER_SERVICE("Controller Service"), + REPORTING_TASK("Reporting Task"), + FLOW_ANALYSIS_RULE("Flow Analysis Rule"), + PARAMETER_CONTEXT("Parameter Context"), + PARAMETER_PROVIDER("Parameter Provider"), + FLOW_REGISTRY_CLIENT("Flow Registry Client"); + + + private final String typeName; + + ComponentType(final String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + @Override + public String toString() { + return typeName; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/ConnectableComponent.java b/src/main/java/org/apache/nifi/flow/ConnectableComponent.java new file mode 100644 index 0000000..c4264db --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ConnectableComponent.java
@@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Objects; + + +public class ConnectableComponent { + private String id; + private String instanceId; + private ConnectableComponentType type; + private String groupId; + private String name; + private String comments; + + @Schema(description = "The id of the connectable component.") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Schema(description = "The instance ID of an existing component that is described by this VersionedComponent, or null if this is not mapped to an instantiated component") + public String getInstanceIdentifier() { + return instanceId; + } + + public void setInstanceIdentifier(String instanceIdentifier) { + this.instanceId = instanceIdentifier; + } + + @Schema(description = "The type of component the connectable is.") + public ConnectableComponentType getType() { + return type; + } + + public void setType(ConnectableComponentType type) { + this.type = type; + } + + @Schema(description = "The id of the group that the connectable component resides in") + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + @Schema(description = "The name of the connectable component") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Schema(description = "The comments for the connectable component.") + public String getComments() { + return comments; + } + + public void setComments(String comments) { + this.comments = comments; + } + + @Override + public int hashCode() { + return Objects.hash(id, groupId, name, type, comments); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof ConnectableComponent)) { + return false; + } + final ConnectableComponent other = (ConnectableComponent) obj; + return Objects.equals(id, other.id); + } + + @Override + public String toString() { + return "ConnectableComponent{" + + "id='" + id + '\'' + + ", instanceId='" + instanceId + '\'' + + ", type=" + type + + ", groupId='" + groupId + '\'' + + ", name='" + name + '\'' + + '}'; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/ConnectableComponentType.java b/src/main/java/org/apache/nifi/flow/ConnectableComponentType.java new file mode 100644 index 0000000..2ad41f8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ConnectableComponentType.java
@@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum ConnectableComponentType { + PROCESSOR, + REMOTE_INPUT_PORT, + REMOTE_OUTPUT_PORT, + INPUT_PORT, + OUTPUT_PORT, + FUNNEL; +}
diff --git a/src/main/java/org/apache/nifi/flow/ControllerServiceAPI.java b/src/main/java/org/apache/nifi/flow/ControllerServiceAPI.java new file mode 100644 index 0000000..ab28fc3 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ControllerServiceAPI.java
@@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import java.util.Objects; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ControllerServiceAPI { + private String type; + private Bundle bundle; + + @Schema(description = "The fully qualified name of the service interface.") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Schema(description = "The details of the artifact that bundled this service interface.") + public Bundle getBundle() { + return bundle; + } + + public void setBundle(Bundle bundle) { + this.bundle = bundle; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ControllerServiceAPI other = (ControllerServiceAPI) o; + return Objects.equals(type, other.type) && Objects.equals(bundle, other.bundle); + } + + @Override + public int hashCode() { + return Objects.hash(type, bundle); + } + +}
diff --git a/src/main/java/org/apache/nifi/flow/ExecutionEngine.java b/src/main/java/org/apache/nifi/flow/ExecutionEngine.java new file mode 100644 index 0000000..9c5412a --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ExecutionEngine.java
@@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum ExecutionEngine { + /** + * Run using the standard NiFi engine + */ + STANDARD, + + /** + * Run using the Stateless engine + */ + STATELESS, + + /** + * Use the Execution Engine that is configured for the parent Process Group. If there is no parent Process Group, default to the standard engine. + */ + INHERITED; +}
diff --git a/src/main/java/org/apache/nifi/flow/ExternalControllerServiceReference.java b/src/main/java/org/apache/nifi/flow/ExternalControllerServiceReference.java new file mode 100644 index 0000000..f7d023e --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ExternalControllerServiceReference.java
@@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema +public class ExternalControllerServiceReference { + + private String identifier; + private String name; + + @Schema(description = "The identifier of the controller service") + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + @Schema(description = "The name of the controller service") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/ParameterProviderReference.java b/src/main/java/org/apache/nifi/flow/ParameterProviderReference.java new file mode 100644 index 0000000..e8c8256 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ParameterProviderReference.java
@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema +public class ParameterProviderReference { + + private String identifier; + private String name; + private String type; + private Bundle bundle; + + @Schema(description = "The fully qualified name of the parameter provider class.") + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + @Schema(description = "The details of the artifact that bundled this parameter provider.") + public Bundle getBundle() { + return bundle; + } + + public void setBundle(final Bundle bundle) { + this.bundle = bundle; + } + + @Schema(description = "The identifier of the parameter provider") + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(final String identifier) { + this.identifier = identifier; + } + + @Schema(description = "The name of the parameter provider") + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/PortType.java b/src/main/java/org/apache/nifi/flow/PortType.java new file mode 100644 index 0000000..86a994e --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/PortType.java
@@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum PortType { + INPUT_PORT, + OUTPUT_PORT; +}
diff --git a/src/main/java/org/apache/nifi/flow/Position.java b/src/main/java/org/apache/nifi/flow/Position.java new file mode 100644 index 0000000..076e713 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/Position.java
@@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + +@Schema(description = "The position of a component on the graph") +public class Position { + private double x; + private double y; + + public Position() { + } + + public Position(double x, double y) { + this.x = x; + this.y = y; + } + + @Schema(description = "The x coordinate.") + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + @Schema(description = "The y coordinate.") + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Override + public String toString() { + return "[x=" + x + ", y=" + y + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Position position = (Position) o; + return Double.compare(position.x, x) == 0 && Double.compare(position.y, y) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } +}
diff --git a/src/main/java/org/apache/nifi/flow/ScheduledState.java b/src/main/java/org/apache/nifi/flow/ScheduledState.java new file mode 100644 index 0000000..d53ede9 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/ScheduledState.java
@@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +public enum ScheduledState { + ENABLED, + DISABLED, + RUNNING; +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedAsset.java b/src/main/java/org/apache/nifi/flow/VersionedAsset.java new file mode 100644 index 0000000..2f1acf6 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedAsset.java
@@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedAsset { + private String identifier; + private String name; + + @Schema(description = "The identifier of the asset") + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(final String identifier) { + this.identifier = identifier; + } + + @Schema(description = "The name of the asset") + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedComponent.java b/src/main/java/org/apache/nifi/flow/VersionedComponent.java new file mode 100644 index 0000000..d645069 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedComponent.java
@@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + + +public abstract class VersionedComponent { + + private String identifier; + private String instanceIdentifier; + private String groupId; + private String name; + private String comments; + private Position position; + + @Schema(description = "The component's unique identifier") + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + @Schema(description = "The instance ID of an existing component that is described by this VersionedComponent, or null if this is not mapped to an instantiated component") + public String getInstanceIdentifier() { + return instanceIdentifier; + } + + public void setInstanceIdentifier(String instanceIdentifier) { + this.instanceIdentifier = instanceIdentifier; + } + + @Schema(description = "The ID of the Process Group that this component belongs to") + public String getGroupIdentifier() { + return groupId; + } + + public void setGroupIdentifier(String groupId) { + this.groupId = groupId; + } + + @Schema(description = "The component's name") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Schema(description = "The component's position on the graph") + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + @Schema(description = "The user-supplied comments for the component") + public String getComments() { + return comments; + } + + public void setComments(String comments) { + this.comments = comments; + } + + public abstract ComponentType getComponentType(); + + public void setComponentType(ComponentType type) { + // purposely do nothing here, this just to allow unmarshalling + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof VersionedComponent)) { + return false; + } + final VersionedComponent other = (VersionedComponent) obj; + return Objects.equals(identifier, other.identifier); + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedConfigurableComponent.java b/src/main/java/org/apache/nifi/flow/VersionedConfigurableComponent.java new file mode 100644 index 0000000..918a2b2 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedConfigurableComponent.java
@@ -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. + */ +package org.apache.nifi.flow; + +import java.util.Map; + +/** + * A component that has property descriptors and can be configured with values for those properties. + */ +public interface VersionedConfigurableComponent { + + Map<String, VersionedPropertyDescriptor> getPropertyDescriptors(); + + void setPropertyDescriptors(Map<String, VersionedPropertyDescriptor> propertyDescriptors); + + Map<String, String> getProperties(); + + void setProperties(Map<String, String> properties); + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedConfigurableExtension.java b/src/main/java/org/apache/nifi/flow/VersionedConfigurableExtension.java new file mode 100644 index 0000000..d21059d --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedConfigurableExtension.java
@@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Map; + +public abstract class VersionedConfigurableExtension extends VersionedComponent + implements VersionedExtensionComponent, VersionedConfigurableComponent { + + private String type; + private Bundle bundle; + + private Map<String, String> properties; + private Map<String, VersionedPropertyDescriptor> propertyDescriptors; + + @Override + @Schema(description = "The type of the extension component") + public String getType() { + return type; + } + + @Override + public void setType(final String type) { + this.type = type; + } + + @Override + @Schema(description = "Information about the bundle from which the component came") + public Bundle getBundle() { + return bundle; + } + + @Override + public void setBundle(Bundle bundle) { + this.bundle = bundle; + } + + + @Override + @Schema(description = "The properties for the component. Properties whose value is not set will only contain the property name.") + public Map<String, String> getProperties() { + return properties; + } + + @Override + public void setProperties(Map<String, String> properties) { + this.properties = properties; + } + + @Override + @Schema(description = "The property descriptors for the component.") + public Map<String, VersionedPropertyDescriptor> getPropertyDescriptors() { + return propertyDescriptors; + } + + @Override + public void setPropertyDescriptors(Map<String, VersionedPropertyDescriptor> propertyDescriptors) { + this.propertyDescriptors = propertyDescriptors; + } + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedConnection.java b/src/main/java/org/apache/nifi/flow/VersionedConnection.java new file mode 100644 index 0000000..c8e36df --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedConnection.java
@@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import java.util.List; +import java.util.Set; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedConnection extends VersionedComponent { + private ConnectableComponent source; + private ConnectableComponent destination; + private Integer labelIndex; + private Long zIndex; + private Set<String> selectedRelationships; + + private Long backPressureObjectThreshold; + private String backPressureDataSizeThreshold; + private String flowFileExpiration; + private List<String> prioritizers; + private List<Position> bends; + + private String loadBalanceStrategy; + private String partitioningAttribute; + private String loadBalanceCompression; + + @Schema(description = "The source of the connection.") + public ConnectableComponent getSource() { + return source; + } + + public void setSource(ConnectableComponent source) { + this.source = source; + } + + @Schema(description = "The destination of the connection.") + public ConnectableComponent getDestination() { + return destination; + } + + public void setDestination(ConnectableComponent destination) { + this.destination = destination; + } + + @Schema(description = "The bend points on the connection.") + public List<Position> getBends() { + return bends; + } + + public void setBends(List<Position> bends) { + this.bends = bends; + } + + @Schema(description = "The index of the bend point where to place the connection label.") + public Integer getLabelIndex() { + return labelIndex; + } + + public void setLabelIndex(Integer labelIndex) { + this.labelIndex = labelIndex; + } + + @Schema( + description = "The z index of the connection.", + name = "zIndex") // Jackson maps this method name to JSON key "zIndex", but Swagger does not by default + public Long getzIndex() { + return zIndex; + } + + public void setzIndex(Long zIndex) { + this.zIndex = zIndex; + } + + @Schema(description = "The selected relationship that comprise the connection.") + public Set<String> getSelectedRelationships() { + return selectedRelationships; + } + + public void setSelectedRelationships(Set<String> relationships) { + this.selectedRelationships = relationships; + } + + @Schema(description = "The object count threshold for determining when back pressure is applied. Updating this value is a passive change in the sense that it won't impact whether existing files " + + "over the limit are affected but it does help feeder processors to stop pushing too much into this work queue.") + public Long getBackPressureObjectThreshold() { + return backPressureObjectThreshold; + } + + public void setBackPressureObjectThreshold(Long backPressureObjectThreshold) { + this.backPressureObjectThreshold = backPressureObjectThreshold; + } + + @Schema(description = "The object data size threshold for determining when back pressure is applied. Updating this value is a passive change in the sense that it won't impact whether existing " + + "files over the limit are affected but it does help feeder processors to stop pushing too much into this work queue.") + public String getBackPressureDataSizeThreshold() { + return backPressureDataSizeThreshold; + } + + public void setBackPressureDataSizeThreshold(String backPressureDataSizeThreshold) { + this.backPressureDataSizeThreshold = backPressureDataSizeThreshold; + } + + + @Schema(description = "The amount of time a flow file may be in the flow before it will be automatically aged out of the flow. Once a flow file reaches this age it will be terminated from " + + "the flow the next time a processor attempts to start work on it.") + public String getFlowFileExpiration() { + return flowFileExpiration; + } + + public void setFlowFileExpiration(String flowFileExpiration) { + this.flowFileExpiration = flowFileExpiration; + } + + + @Schema(description = "The comparators used to prioritize the queue.") + public List<String> getPrioritizers() { + return prioritizers; + } + + public void setPrioritizers(List<String> prioritizers) { + this.prioritizers = prioritizers; + } + + @Schema(description = "The Strategy to use for load balancing data across the cluster, or null, if no Load Balance Strategy has been specified.", + allowableValues = "DO_NOT_LOAD_BALANCE, PARTITION_BY_ATTRIBUTE, ROUND_ROBIN, SINGLE_NODE") + public String getLoadBalanceStrategy() { + return loadBalanceStrategy; + } + + public void setLoadBalanceStrategy(String loadBalanceStrategy) { + this.loadBalanceStrategy = loadBalanceStrategy; + } + + @Schema(description = "The attribute to use for partitioning data as it is load balanced across the cluster. If the Load Balance Strategy is configured to use PARTITION_BY_ATTRIBUTE, the value " + + "returned by this method is the name of the FlowFile Attribute that will be used to determine which node in the cluster should receive a given FlowFile. If the Load Balance Strategy is " + + "unset or is set to any other value, the Partitioning Attribute has no effect.") + public String getPartitioningAttribute() { + return partitioningAttribute; + } + + public void setPartitioningAttribute(final String partitioningAttribute) { + this.partitioningAttribute = partitioningAttribute; + } + + @Schema(description = "Whether or not compression should be used when transferring FlowFiles between nodes", + allowableValues = "DO_NOT_COMPRESS, COMPRESS_ATTRIBUTES_ONLY, COMPRESS_ATTRIBUTES_AND_CONTENT") + public String getLoadBalanceCompression() { + return loadBalanceCompression; + } + + public void setLoadBalanceCompression(final String compression) { + this.loadBalanceCompression = compression; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.CONNECTION; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedControllerService.java b/src/main/java/org/apache/nifi/flow/VersionedControllerService.java new file mode 100644 index 0000000..25b857b --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedControllerService.java
@@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +public class VersionedControllerService extends VersionedConfigurableExtension { + + private List<ControllerServiceAPI> controllerServiceApis; + + private String annotationData; + private ScheduledState scheduledState; + private String bulletinLevel; + + @Schema(description = "Lists the APIs this Controller Service implements.") + public List<ControllerServiceAPI> getControllerServiceApis() { + return controllerServiceApis; + } + + public void setControllerServiceApis(List<ControllerServiceAPI> controllerServiceApis) { + this.controllerServiceApis = controllerServiceApis; + } + + @Schema(description = "The annotation for the controller service. This is how the custom UI relays configuration to the controller service.") + public String getAnnotationData() { + return annotationData; + } + + public void setAnnotationData(String annotationData) { + this.annotationData = annotationData; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.CONTROLLER_SERVICE; + } + + @Schema(description = "The ScheduledState denoting whether the Controller Service is ENABLED or DISABLED") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(final ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } + + @Schema(description = "The level at which the controller service will report bulletins.") + public String getBulletinLevel() { + return bulletinLevel; + } + + public void setBulletinLevel(String bulletinLevel) { + this.bulletinLevel = bulletinLevel; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedExtensionComponent.java b/src/main/java/org/apache/nifi/flow/VersionedExtensionComponent.java new file mode 100644 index 0000000..f2094ba --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedExtensionComponent.java
@@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +/** + * A component that is an extension and has a type and bundle. + */ +public interface VersionedExtensionComponent { + + Bundle getBundle(); + + void setBundle(Bundle bundle); + + String getType(); + + void setType(String type); + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedExternalFlow.java b/src/main/java/org/apache/nifi/flow/VersionedExternalFlow.java new file mode 100644 index 0000000..afea048 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedExternalFlow.java
@@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import java.util.Map; + +public class VersionedExternalFlow { + private VersionedProcessGroup flowContents; + private Map<String, ExternalControllerServiceReference> externalControllerServices; + private Map<String, VersionedParameterContext> parameterContexts; + private Map<String, ParameterProviderReference> parameterProviders; + private VersionedExternalFlowMetadata metadata; + + public VersionedProcessGroup getFlowContents() { + return flowContents; + } + + public void setFlowContents(final VersionedProcessGroup flowContents) { + this.flowContents = flowContents; + } + + public Map<String, ExternalControllerServiceReference> getExternalControllerServices() { + return externalControllerServices; + } + + public void setExternalControllerServices(final Map<String, ExternalControllerServiceReference> externalControllerServices) { + this.externalControllerServices = externalControllerServices; + } + + public Map<String, VersionedParameterContext> getParameterContexts() { + return parameterContexts; + } + + public void setParameterContexts(final Map<String, VersionedParameterContext> parameterContexts) { + this.parameterContexts = parameterContexts; + } + + public VersionedExternalFlowMetadata getMetadata() { + return metadata; + } + + public void setMetadata(final VersionedExternalFlowMetadata metadata) { + this.metadata = metadata; + } + + public Map<String, ParameterProviderReference> getParameterProviders() { + return parameterProviders; + } + + public void setParameterProviders(final Map<String, ParameterProviderReference> parameterProviders) { + this.parameterProviders = parameterProviders; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedExternalFlowMetadata.java b/src/main/java/org/apache/nifi/flow/VersionedExternalFlowMetadata.java new file mode 100644 index 0000000..09f32d3 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedExternalFlowMetadata.java
@@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public class VersionedExternalFlowMetadata { + private String bucketId; + private String flowId; + private String version; + private String flowName; + private String author; + private String comments; + private long timestamp; + + public String getBucketIdentifier() { + return bucketId; + } + + public void setBucketIdentifier(final String bucketIdentifier) { + this.bucketId = bucketIdentifier; + } + + public String getFlowIdentifier() { + return flowId; + } + + public void setFlowIdentifier(final String flowId) { + this.flowId = flowId; + } + + public String getVersion() { + return version; + } + + public void setVersion(final String version) { + this.version = version; + } + + public String getFlowName() { + return flowName; + } + + public void setFlowName(final String flowName) { + this.flowName = flowName; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(final String author) { + this.author = author; + } + + public String getComments() { + return comments; + } + + public void setComments(final String comments) { + this.comments = comments; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(final long timestamp) { + this.timestamp = timestamp; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedFlowAnalysisRule.java b/src/main/java/org/apache/nifi/flow/VersionedFlowAnalysisRule.java new file mode 100644 index 0000000..0d5abd8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedFlowAnalysisRule.java
@@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.nifi.flowanalysis.EnforcementPolicy; + +public class VersionedFlowAnalysisRule extends VersionedConfigurableExtension { + + private ScheduledState scheduledState; + private EnforcementPolicy enforcementPolicy; + + @Schema(description = "How to handle violations.") + public EnforcementPolicy getEnforcementPolicy() { + return enforcementPolicy; + } + + public void setEnforcementPolicy(EnforcementPolicy enforcementPolicy) { + this.enforcementPolicy = enforcementPolicy; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.FLOW_ANALYSIS_RULE; + } + + @Schema(description = "Indicates the scheduled state for the flow analysis rule") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(final ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedFlowCoordinates.java b/src/main/java/org/apache/nifi/flow/VersionedFlowCoordinates.java new file mode 100644 index 0000000..f96b36d --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedFlowCoordinates.java
@@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + +public class VersionedFlowCoordinates { + private String registryId; + private String storageLocation; + private String branch; + private String bucketId; + private String flowId; + private String version; + private Boolean latest; + + @Schema(description = "The identifier of the Flow Registry that contains the flow") + public String getRegistryId() { + return registryId; + } + + public void setRegistryId(String registryId) { + this.registryId = registryId; + } + + @Schema(description = "The location of the Flow Registry that stores the flow") + public String getStorageLocation() { + return storageLocation; + } + + public void setStorageLocation(String storageLocation) { + this.storageLocation = storageLocation; + } + + @Schema(description = "The name of the branch that the flow resides in") + public String getBranch() { + return branch; + } + + public void setBranch(final String branch) { + this.branch = branch; + } + + @Schema(description = "The UUID of the bucket that the flow resides in") + public String getBucketId() { + return bucketId; + } + + public void setBucketId(String bucketId) { + this.bucketId = bucketId; + } + + @Schema(description = "The UUID of the flow") + public String getFlowId() { + return flowId; + } + + public void setFlowId(String flowId) { + this.flowId = flowId; + } + + @Schema(description = "The version of the flow") + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + @Schema(description = "Whether or not these coordinates point to the latest version of the flow") + public Boolean getLatest() { + return latest; + } + + public void setLatest(Boolean latest) { + this.latest = latest; + } + + @Override + public int hashCode() { + return Objects.hash(registryId, storageLocation, branch, bucketId, flowId, version); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof final VersionedFlowCoordinates other)) { + return false; + } + + return Objects.equals(storageLocation, other.storageLocation) + && Objects.equals(branch, other.branch) + && Objects.equals(bucketId, other.bucketId) + && Objects.equals(flowId, other.flowId) + && Objects.equals(version, other.version); + } + + @Override + public String toString() { + return "VersionedFlowCoordinates[branch=" + branch + ", bucketId=" + bucketId + ", flowId=" + flowId + ", version=" + version + ", storageLocation=" + storageLocation + "]"; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedFlowRegistryClient.java b/src/main/java/org/apache/nifi/flow/VersionedFlowRegistryClient.java new file mode 100644 index 0000000..3b4c9eb --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedFlowRegistryClient.java
@@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedFlowRegistryClient extends VersionedConfigurableExtension { + private String description; + private String annotationData; + + @Override + public ComponentType getComponentType() { + return ComponentType.FLOW_REGISTRY_CLIENT; + } + + @Schema(description = "The description of the registry") + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + @Schema(description = "The annotation for the reporting task. This is how the custom UI relays configuration to the reporting task.") + public String getAnnotationData() { + return annotationData; + } + + public void setAnnotationData(String annotationData) { + this.annotationData = annotationData; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedFunnel.java b/src/main/java/org/apache/nifi/flow/VersionedFunnel.java new file mode 100644 index 0000000..aa66d92 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedFunnel.java
@@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public class VersionedFunnel extends VersionedComponent { + @Override + public ComponentType getComponentType() { + return ComponentType.FUNNEL; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedLabel.java b/src/main/java/org/apache/nifi/flow/VersionedLabel.java new file mode 100644 index 0000000..d0e8e24 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedLabel.java
@@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedLabel extends VersionedComponent { + private String label; + private Long zIndex; + + private Double width; + private Double height; + + private Map<String, String> style; + + + @Schema(description = "The text that appears in the label.") + public String getLabel() { + return label; + } + + public void setLabel(final String label) { + this.label = label; + } + + @Schema(description = "The styles for this label (font-size : 12px, background-color : #eee, etc).") + public Map<String, String> getStyle() { + return style; + } + + public void setStyle(final Map<String, String> style) { + this.style = style; + } + + @Schema(description = "The height of the label in pixels when at a 1:1 scale.") + public Double getHeight() { + return height; + } + + public void setHeight(Double height) { + this.height = height; + } + + @Schema(description = "The width of the label in pixels when at a 1:1 scale.") + public Double getWidth() { + return width; + } + + public void setWidth(Double width) { + this.width = width; + } + + @Schema( + description = "The z index of the connection.", + name = "zIndex") // Jackson maps this method name to JSON key "zIndex", but Swagger does not by default + public Long getzIndex() { + return zIndex; + } + + public void setzIndex(Long zIndex) { + this.zIndex = zIndex; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.LABEL; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedParameter.java b/src/main/java/org/apache/nifi/flow/VersionedParameter.java new file mode 100644 index 0000000..35dd4f8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedParameter.java
@@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; +import java.util.Objects; + +public class VersionedParameter { + + private String name; + private String description; + private boolean sensitive; + private boolean provided; + private String value; + private List<VersionedAsset> referencedAssets; + + @Schema(description = "The name of the parameter") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Schema(description = "The description of the param") + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Schema(description = "Whether or not the parameter value is sensitive") + public boolean isSensitive() { + return sensitive; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Schema(description = "Whether or not the parameter value is provided by a ParameterProvider") + public boolean isProvided() { + return provided; + } + + public void setProvided(boolean provided) { + this.provided = provided; + } + + @Schema(description = "The value of the parameter") + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Schema(description = "The assets that are referenced by this parameter") + public List<VersionedAsset> getReferencedAssets() { + return referencedAssets; + } + + public void setReferencedAssets(final List<VersionedAsset> referencedAssets) { + this.referencedAssets = referencedAssets; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VersionedParameter that = (VersionedParameter) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedParameterContext.java b/src/main/java/org/apache/nifi/flow/VersionedParameterContext.java new file mode 100644 index 0000000..717ccbd --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedParameterContext.java
@@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; +import java.util.Set; + +public class VersionedParameterContext extends VersionedComponent { + private Set<VersionedParameter> parameters; + private List<String> inheritedParameterContexts; + private String description; + private String parameterProvider; + private String parameterGroupName; + private Boolean isSynchronized; + + @Schema(description = "The description of the parameter context") + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Schema(description = "The parameters in the context") + public Set<VersionedParameter> getParameters() { + return parameters; + } + + public void setParameters(Set<VersionedParameter> parameters) { + this.parameters = parameters; + } + + @Schema(description = "The names of additional parameter contexts from which to inherit parameters") + public List<String> getInheritedParameterContexts() { + return inheritedParameterContexts; + } + + public void setInheritedParameterContexts(List<String> parameterContextNames) { + this.inheritedParameterContexts = parameterContextNames; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.PARAMETER_CONTEXT; + } + + @Schema(description = "The identifier of an optional parameter provider") + public String getParameterProvider() { + return parameterProvider; + } + + public void setParameterProvider(String parameterProvider) { + this.parameterProvider = parameterProvider; + } + + @Schema(description = "The corresponding parameter group name fetched from the parameter provider, if applicable") + public String getParameterGroupName() { + return parameterGroupName; + } + + public void setParameterGroupName(String parameterGroupName) { + this.parameterGroupName = parameterGroupName; + } + + @Schema(description = "True if the parameter provider is set and the context should receive updates when its parameters are next fetched") + public Boolean isSynchronized() { + return isSynchronized; + } + + public void setSynchronized(Boolean aSynchronized) { + isSynchronized = aSynchronized; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedParameterProvider.java b/src/main/java/org/apache/nifi/flow/VersionedParameterProvider.java new file mode 100644 index 0000000..576b1d0 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedParameterProvider.java
@@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Map; + +public class VersionedParameterProvider extends VersionedComponent implements VersionedConfigurableComponent, VersionedExtensionComponent { + private String type; + private Bundle bundle; + private Map<String, String> properties; + private Map<String, VersionedPropertyDescriptor> propertyDescriptors; + private String annotationData; + + + @Override + @Schema(description = "The type of the parameter provider.") + public String getType() { + return type; + } + + @Override + public void setType(final String type) { + this.type = type; + } + + @Override + @Schema(description = "The details of the artifact that bundled this parameter provider type.") + public Bundle getBundle() { + return bundle; + } + + @Override + public void setBundle(Bundle bundle) { + this.bundle = bundle; + } + + @Override + @Schema(description = "The properties of the parameter provider.") + public Map<String, String> getProperties() { + return properties; + } + + @Override + public void setProperties(Map<String, String> properties) { + this.properties = properties; + } + + @Override + @Schema(description = "The property descriptors for the parameter provider.") + public Map<String, VersionedPropertyDescriptor> getPropertyDescriptors() { + return propertyDescriptors; + } + + @Override + public void setPropertyDescriptors(Map<String, VersionedPropertyDescriptor> propertyDescriptors) { + this.propertyDescriptors = propertyDescriptors; + } + + @Schema(description = "The annotation for the parameter provider. This is how the custom UI relays configuration to the parameter provider.") + public String getAnnotationData() { + return annotationData; + } + + public void setAnnotationData(String annotationData) { + this.annotationData = annotationData; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.PARAMETER_PROVIDER; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedPort.java b/src/main/java/org/apache/nifi/flow/VersionedPort.java new file mode 100644 index 0000000..cd052c4 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedPort.java
@@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.nifi.components.PortFunction; + +public class VersionedPort extends VersionedComponent { + private PortType type; + private Integer concurrentlySchedulableTaskCount; + private ScheduledState scheduledState; + private Boolean allowRemoteAccess; + private PortFunction portFunction; + + @Schema(description = "The number of tasks that should be concurrently scheduled for the port.") + public Integer getConcurrentlySchedulableTaskCount() { + return concurrentlySchedulableTaskCount; + } + + public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) { + this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount; + } + + @Schema(description = "The type of port.") + public PortType getType() { + return type; + } + + public void setType(PortType type) { + this.type = type; + } + + @Schema(description = "The scheduled state of the component") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } + + @Schema(description = "Whether or not this port allows remote access for site-to-site") + public Boolean isAllowRemoteAccess() { + return allowRemoteAccess != null && allowRemoteAccess; + } + + public Boolean getAllowRemoteAccess() { + return allowRemoteAccess; + } + + public void setAllowRemoteAccess(Boolean allowRemoteAccess) { + this.allowRemoteAccess = allowRemoteAccess; + } + + @Schema(description = "Specifies how the Port should function") + public PortFunction getPortFunction() { + return portFunction; + } + + public void setPortFunction(final PortFunction portFunction) { + this.portFunction = portFunction; + } + + @Override + public ComponentType getComponentType() { + if (type == PortType.OUTPUT_PORT) { + return ComponentType.OUTPUT_PORT; + } + + return ComponentType.INPUT_PORT; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedProcessGroup.java b/src/main/java/org/apache/nifi/flow/VersionedProcessGroup.java new file mode 100644 index 0000000..e63c59b --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedProcessGroup.java
@@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.HashSet; +import java.util.Set; + +public class VersionedProcessGroup extends VersionedComponent { + + private Set<VersionedProcessGroup> processGroups = new HashSet<>(); + private Set<VersionedRemoteProcessGroup> remoteProcessGroups = new HashSet<>(); + private Set<VersionedProcessor> processors = new HashSet<>(); + private Set<VersionedPort> inputPorts = new HashSet<>(); + private Set<VersionedPort> outputPorts = new HashSet<>(); + private Set<VersionedConnection> connections = new HashSet<>(); + private Set<VersionedLabel> labels = new HashSet<>(); + private Set<VersionedFunnel> funnels = new HashSet<>(); + private Set<VersionedControllerService> controllerServices = new HashSet<>(); + private VersionedFlowCoordinates versionedFlowCoordinates = null; + + private String parameterContextName; + private String flowfileConcurrency; + private String flowfileOutboundPolicy; + + private String defaultFlowFileExpiration; + private Long defaultBackPressureObjectThreshold; + private String defaultBackPressureDataSizeThreshold; + private ScheduledState scheduledState; + private ExecutionEngine executionEngine; + private Integer maxConcurrentTasks; + private String statelessFlowTimeout; + + private String logFileSuffix; + + + @Schema(description = "The child Process Groups") + public Set<VersionedProcessGroup> getProcessGroups() { + return processGroups; + } + + public void setProcessGroups(Set<VersionedProcessGroup> processGroups) { + this.processGroups = new HashSet<>(processGroups); + } + + @Schema(description = "The Remote Process Groups") + public Set<VersionedRemoteProcessGroup> getRemoteProcessGroups() { + return remoteProcessGroups; + } + + public void setRemoteProcessGroups(Set<VersionedRemoteProcessGroup> remoteProcessGroups) { + this.remoteProcessGroups = new HashSet<>(remoteProcessGroups); + } + + @Schema(description = "The Processors") + public Set<VersionedProcessor> getProcessors() { + return processors; + } + + public void setProcessors(Set<VersionedProcessor> processors) { + this.processors = new HashSet<>(processors); + } + + @Schema(description = "The Input Ports") + public Set<VersionedPort> getInputPorts() { + return inputPorts; + } + + public void setInputPorts(Set<VersionedPort> inputPorts) { + this.inputPorts = new HashSet<>(inputPorts); + } + + @Schema(description = "The Output Ports") + public Set<VersionedPort> getOutputPorts() { + return outputPorts; + } + + public void setOutputPorts(Set<VersionedPort> outputPorts) { + this.outputPorts = new HashSet<>(outputPorts); + } + + @Schema(description = "The Connections") + public Set<VersionedConnection> getConnections() { + return connections; + } + + public void setConnections(Set<VersionedConnection> connections) { + this.connections = new HashSet<>(connections); + } + + @Schema(description = "The Labels") + public Set<VersionedLabel> getLabels() { + return labels; + } + + public void setLabels(Set<VersionedLabel> labels) { + this.labels = new HashSet<>(labels); + } + + @Schema(description = "The Funnels") + public Set<VersionedFunnel> getFunnels() { + return funnels; + } + + public void setFunnels(Set<VersionedFunnel> funnels) { + this.funnels = new HashSet<>(funnels); + } + + @Schema(description = "The Controller Services") + public Set<VersionedControllerService> getControllerServices() { + return controllerServices; + } + + public void setControllerServices(Set<VersionedControllerService> controllerServices) { + this.controllerServices = new HashSet<>(controllerServices); + } + + @Override + public ComponentType getComponentType() { + return ComponentType.PROCESS_GROUP; + } + + public void setVersionedFlowCoordinates(VersionedFlowCoordinates flowCoordinates) { + this.versionedFlowCoordinates = flowCoordinates; + } + + @Schema(description = "The coordinates where the remote flow is stored, or null if the Process Group is not directly under Version Control") + public VersionedFlowCoordinates getVersionedFlowCoordinates() { + return versionedFlowCoordinates; + } + + @Schema(description = "The name of the parameter context used by this process group") + public String getParameterContextName() { + return parameterContextName; + } + + public void setParameterContextName(String parameterContextName) { + this.parameterContextName = parameterContextName; + } + + @Schema(description = "The configured FlowFile Concurrency for the Process Group") + public String getFlowFileConcurrency() { + return flowfileConcurrency; + } + + public void setFlowFileConcurrency(final String flowfileConcurrency) { + this.flowfileConcurrency = flowfileConcurrency; + } + + @Schema(description = "The FlowFile Outbound Policy for the Process Group") + public String getFlowFileOutboundPolicy() { + return flowfileOutboundPolicy; + } + + public void setFlowFileOutboundPolicy(final String outboundPolicy) { + this.flowfileOutboundPolicy = outboundPolicy; + } + + @Schema(description = "The default FlowFile Expiration for this Process Group.") + public String getDefaultFlowFileExpiration() { + return defaultFlowFileExpiration; + } + + public void setDefaultFlowFileExpiration(String defaultFlowFileExpiration) { + this.defaultFlowFileExpiration = defaultFlowFileExpiration; + } + + @Schema(description = "Default value used in this Process Group for the maximum number of objects that can be queued before back pressure is applied.") + public Long getDefaultBackPressureObjectThreshold() { + return defaultBackPressureObjectThreshold; + } + + public void setDefaultBackPressureObjectThreshold(final Long defaultBackPressureObjectThreshold) { + this.defaultBackPressureObjectThreshold = defaultBackPressureObjectThreshold; + } + + @Schema(description = "Default value used in this Process Group for the maximum data size of objects that can be queued before back pressure is applied.") + public String getDefaultBackPressureDataSizeThreshold() { + return defaultBackPressureDataSizeThreshold; + } + + public void setDefaultBackPressureDataSizeThreshold(final String defaultBackPressureDataSizeThreshold) { + this.defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThreshold; + } + + @Schema(description = "The log file suffix for this Process Group for dedicated logging.") + public String getLogFileSuffix() { + return logFileSuffix; + } + + public void setLogFileSuffix(final String logFileSuffix) { + this.logFileSuffix = logFileSuffix; + } + + @Schema(description = "The Scheduled State of the Process Group, if the group is configured to use the Stateless Execution Engine. Otherwise, this value has no relevance.") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(final ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } + + @Schema(description = "The Execution Engine that should be used to run the components within the group.") + public ExecutionEngine getExecutionEngine() { + return executionEngine; + } + + public void setExecutionEngine(final ExecutionEngine executionEngine) { + this.executionEngine = executionEngine; + } + + @Schema(description = "The maximum number of concurrent tasks that should be scheduled for this Process Group when using the Stateless Engine") + public Integer getMaxConcurrentTasks() { + return maxConcurrentTasks; + } + + public void setMaxConcurrentTasks(final Integer maxConcurrentTasks) { + this.maxConcurrentTasks = maxConcurrentTasks; + } + + @Schema(description = "The maximum amount of time that the flow is allows to run using the Stateless engine before it times out and is considered a failure") + public String getStatelessFlowTimeout() { + return statelessFlowTimeout; + } + + public void setStatelessFlowTimeout(final String timeout) { + this.statelessFlowTimeout = timeout; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedProcessor.java b/src/main/java/org/apache/nifi/flow/VersionedProcessor.java new file mode 100644 index 0000000..266c6e9 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedProcessor.java
@@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Map; +import java.util.Set; + +public class VersionedProcessor extends VersionedConfigurableExtension { + + private Map<String, String> style; + private String annotationData; + + private String schedulingPeriod; + private String schedulingStrategy; + private String executionNode; + private String penaltyDuration; + private String yieldDuration; + private String bulletinLevel; + private Long runDurationMillis; + private Integer concurrentlySchedulableTaskCount; + private Set<String> autoTerminatedRelationships; + private ScheduledState scheduledState; + + private Integer retryCount; + private Set<String> retriedRelationships; + private String backoffMechanism; + private String maxBackoffPeriod; + + @Schema(description = "The frequency with which to schedule the processor. The format of the value will depend on th value of schedulingStrategy.") + public String getSchedulingPeriod() { + return schedulingPeriod; + } + + public void setSchedulingPeriod(String setSchedulingPeriod) { + this.schedulingPeriod = setSchedulingPeriod; + } + + @Schema(description = "Indicates how the processor should be scheduled to run.") + public String getSchedulingStrategy() { + return schedulingStrategy; + } + + public void setSchedulingStrategy(String schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + } + + @Schema(description = "Indicates the node where the process will execute.") + public String getExecutionNode() { + return executionNode; + } + + public void setExecutionNode(String executionNode) { + this.executionNode = executionNode; + } + + @Schema(description = "The amout of time that is used when the process penalizes a flowfile.") + public String getPenaltyDuration() { + return penaltyDuration; + } + + public void setPenaltyDuration(String penaltyDuration) { + this.penaltyDuration = penaltyDuration; + } + + @Schema(description = "The amount of time that must elapse before this processor is scheduled again after yielding.") + public String getYieldDuration() { + return yieldDuration; + } + + public void setYieldDuration(String yieldDuration) { + this.yieldDuration = yieldDuration; + } + + @Schema(description = "The level at which the processor will report bulletins.") + public String getBulletinLevel() { + return bulletinLevel; + } + + public void setBulletinLevel(String bulletinLevel) { + this.bulletinLevel = bulletinLevel; + } + + @Schema(description = "The number of tasks that should be concurrently schedule for the processor. If the processor doesn't allow parallol processing then any positive input will be ignored.") + public Integer getConcurrentlySchedulableTaskCount() { + return concurrentlySchedulableTaskCount; + } + + public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) { + this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount; + } + + @Schema(description = "The annotation data for the processor used to relay configuration between a custom UI and the procesosr.") + public String getAnnotationData() { + return annotationData; + } + + public void setAnnotationData(String annotationData) { + this.annotationData = annotationData; + } + + + @Schema(description = "The names of all relationships that cause a flow file to be terminated if the relationship is not connected elsewhere. This property differs " + + "from the 'isAutoTerminate' property of the RelationshipDTO in that the RelationshipDTO is meant to depict the current configuration, whereas this " + + "property can be set in a DTO when updating a Processor in order to change which Relationships should be auto-terminated.") + public Set<String> getAutoTerminatedRelationships() { + return autoTerminatedRelationships; + } + + public void setAutoTerminatedRelationships(final Set<String> autoTerminatedRelationships) { + this.autoTerminatedRelationships = autoTerminatedRelationships; + } + + @Schema(description = "The run duration for the processor in milliseconds.") + public Long getRunDurationMillis() { + return runDurationMillis; + } + + public void setRunDurationMillis(Long runDurationMillis) { + this.runDurationMillis = runDurationMillis; + } + + @Schema(description = "Stylistic data for rendering in a UI") + public Map<String, String> getStyle() { + return style; + } + + public void setStyle(Map<String, String> style) { + this.style = style; + } + + @Schema(description = "The scheduled state of the component") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.PROCESSOR; + } + + @Schema(description = "Overall number of retries.") + public Integer getRetryCount() { + return retryCount; + } + + public void setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + } + + @Schema(description = "All the relationships should be retried.") + public Set<String> getRetriedRelationships() { + return retriedRelationships; + } + + public void setRetriedRelationships(Set<String> retriedRelationships) { + this.retriedRelationships = retriedRelationships; + } + + @Schema( + description = "Determines whether the FlowFile should be penalized or the processor should be yielded between retries.", + allowableValues = "PENALIZE_FLOWFILE, YIELD_PROCESSOR" + ) + public String getBackoffMechanism() { + return backoffMechanism; + } + + public void setBackoffMechanism(String backoffMechanism) { + this.backoffMechanism = backoffMechanism; + } + + @Schema(description = "Maximum amount of time to be waited during a retry period.") + public String getMaxBackoffPeriod() { + return maxBackoffPeriod; + } + + public void setMaxBackoffPeriod(String maxBackoffPeriod) { + this.maxBackoffPeriod = maxBackoffPeriod; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java b/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java new file mode 100644 index 0000000..1540935 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java
@@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedPropertyDescriptor { + private String name; + private String displayName; + private boolean identifiesControllerService; + private boolean sensitive; + private boolean dynamic; + private VersionedResourceDefinition resourceDefinition; + + @Schema(description = "The name of the property") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Schema(description = "The display name of the property") + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Schema(description = "Whether or not the property provides the identifier of a Controller Service") + public boolean getIdentifiesControllerService() { + return identifiesControllerService; + } + + public void setIdentifiesControllerService(boolean identifiesControllerService) { + this.identifiesControllerService = identifiesControllerService; + } + + @Schema(description = "Whether or not the property is considered sensitive") + public boolean isSensitive() { + return sensitive; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Schema(description = "Whether or not the property is user-defined") + public boolean isDynamic() { + return dynamic; + } + + public void setDynamic(boolean dynamic) { + this.dynamic = dynamic; + } + + @Schema(description = "Returns the Resource Definition that defines which type(s) of resource(s) this property references, if any") + public VersionedResourceDefinition getResourceDefinition() { + return resourceDefinition; + } + + public void setResourceDefinition(final VersionedResourceDefinition resourceDefinition) { + this.resourceDefinition = resourceDefinition; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedRemoteGroupPort.java b/src/main/java/org/apache/nifi/flow/VersionedRemoteGroupPort.java new file mode 100644 index 0000000..777866a --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedRemoteGroupPort.java
@@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + +public class VersionedRemoteGroupPort extends VersionedComponent { + private String remoteGroupId; + private Integer concurrentlySchedulableTaskCount; + private Boolean useCompression; + private BatchSize batchSize; + private ComponentType componentType; + private String targetId; + private ScheduledState scheduledState; + + @Schema(description = "The number of task that may transmit flowfiles to the target port concurrently.") + public Integer getConcurrentlySchedulableTaskCount() { + return concurrentlySchedulableTaskCount; + } + + public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) { + this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount; + } + + @Schema(description = "The id of the remote process group that the port resides in.") + public String getRemoteGroupId() { + return remoteGroupId; + } + + public void setRemoteGroupId(String groupId) { + this.remoteGroupId = groupId; + } + + + @Schema(description = "Whether the flowfiles are compressed when sent to the target port.") + public Boolean isUseCompression() { + return useCompression; + } + + public void setUseCompression(Boolean useCompression) { + this.useCompression = useCompression; + } + + @Schema(description = "The batch settings for data transmission.") + public BatchSize getBatchSize() { + return batchSize; + } + + public void setBatchSize(BatchSize batchSize) { + this.batchSize = batchSize; + } + + @Schema(description = "The ID of the port on the target NiFi instance") + public String getTargetId() { + return targetId; + } + + public void setTargetId(final String targetId) { + this.targetId = targetId; + } + + @Schema(description = "The scheduled state of the component") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } + + @Override + public int hashCode() { + return 923847 + String.valueOf(getName()).hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof VersionedRemoteGroupPort)) { + return false; + } + + final VersionedRemoteGroupPort other = (VersionedRemoteGroupPort) obj; + return Objects.equals(getName(), other.getName()); + } + + @Override + public ComponentType getComponentType() { + return componentType; + } + + @Override + public void setComponentType(final ComponentType componentType) { + if (componentType != ComponentType.REMOTE_INPUT_PORT && componentType != ComponentType.REMOTE_OUTPUT_PORT) { + throw new IllegalArgumentException(); + } + + this.componentType = componentType; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedRemoteProcessGroup.java b/src/main/java/org/apache/nifi/flow/VersionedRemoteProcessGroup.java new file mode 100644 index 0000000..b2fdd14 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedRemoteProcessGroup.java
@@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Set; + +public class VersionedRemoteProcessGroup extends VersionedComponent { + private String targetUris; + + private String communicationsTimeout; + private String yieldDuration; + private String transportProtocol; + private String localNetworkInterface; + private String proxyHost; + private Integer proxyPort; + private String proxyUser; + private String proxyPassword; + + private Set<VersionedRemoteGroupPort> inputPorts; + private Set<VersionedRemoteGroupPort> outputPorts; + + @Schema(description = "The target URIs of the remote process group. " + + "If target uris is not set but target uri is set, then returns the single target uri. " + + "If neither target uris nor target uri is set, then returns null.") + public String getTargetUris() { + return !isEmpty(targetUris) ? targetUris : null; + + } + + private boolean isEmpty(final String value) { + return value == null || value.isEmpty(); + } + + public void setTargetUris(String targetUris) { + this.targetUris = targetUris; + } + + @Schema(description = "The time period used for the timeout when communicating with the target.") + public String getCommunicationsTimeout() { + return communicationsTimeout; + } + + public void setCommunicationsTimeout(String communicationsTimeout) { + this.communicationsTimeout = communicationsTimeout; + } + + @Schema(description = "When yielding, this amount of time must elapse before the remote process group is scheduled again.") + public String getYieldDuration() { + return yieldDuration; + } + + public void setYieldDuration(String yieldDuration) { + this.yieldDuration = yieldDuration; + } + + @Schema(description = "The Transport Protocol that is used for Site-to-Site communications", allowableValues = "RAW, HTTP") + public String getTransportProtocol() { + return transportProtocol; + } + + public void setTransportProtocol(String transportProtocol) { + this.transportProtocol = transportProtocol; + } + + @Schema(description = "A Set of Input Ports that can be connected to, in order to send data to the remote NiFi instance") + public Set<VersionedRemoteGroupPort> getInputPorts() { + return inputPorts; + } + + public void setInputPorts(Set<VersionedRemoteGroupPort> inputPorts) { + this.inputPorts = inputPorts; + } + + @Schema(description = "A Set of Output Ports that can be connected to, in order to pull data from the remote NiFi instance") + public Set<VersionedRemoteGroupPort> getOutputPorts() { + return outputPorts; + } + + public void setOutputPorts(Set<VersionedRemoteGroupPort> outputPorts) { + this.outputPorts = outputPorts; + } + + + @Schema(description = "The local network interface to send/receive data. If not specified, any local address is used. If clustered, all nodes must have an interface with this identifier.") + public String getLocalNetworkInterface() { + return localNetworkInterface; + } + + public void setLocalNetworkInterface(String localNetworkInterface) { + this.localNetworkInterface = localNetworkInterface; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public Integer getProxyPort() { + return proxyPort; + } + + public void setProxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + } + + public String getProxyUser() { + return proxyUser; + } + + public void setProxyUser(String proxyUser) { + this.proxyUser = proxyUser; + } + + public String getProxyPassword() { + return proxyPassword; + } + + public void setProxyPassword(String proxyPassword) { + this.proxyPassword = proxyPassword; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.REMOTE_PROCESS_GROUP; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedReportingTask.java b/src/main/java/org/apache/nifi/flow/VersionedReportingTask.java new file mode 100644 index 0000000..6e9cc03 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedReportingTask.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class VersionedReportingTask extends VersionedConfigurableExtension { + + private String annotationData; + private ScheduledState scheduledState; + private String schedulingPeriod; + private String schedulingStrategy; + + @Schema(description = "The annotation for the reporting task. This is how the custom UI relays configuration to the reporting task.") + public String getAnnotationData() { + return annotationData; + } + + public void setAnnotationData(String annotationData) { + this.annotationData = annotationData; + } + + @Schema(description = "The frequency with which to schedule the reporting task. The format of the value will depend on the value of schedulingStrategy.") + public String getSchedulingPeriod() { + return schedulingPeriod; + } + + public void setSchedulingPeriod(String setSchedulingPeriod) { + this.schedulingPeriod = setSchedulingPeriod; + } + + @Schema(description = "Indicates scheduling strategy that should dictate how the reporting task is triggered.") + public String getSchedulingStrategy() { + return schedulingStrategy; + } + + public void setSchedulingStrategy(String schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + } + + @Override + public ComponentType getComponentType() { + return ComponentType.REPORTING_TASK; + } + + @Schema(description = "Indicates the scheduled state for the Reporting Task") + public ScheduledState getScheduledState() { + return scheduledState; + } + + public void setScheduledState(final ScheduledState scheduledState) { + this.scheduledState = scheduledState; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java b/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java new file mode 100644 index 0000000..d1d7e53 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java
@@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +@Schema +public class VersionedReportingTaskSnapshot { + + private List<VersionedReportingTask> reportingTasks; + private List<VersionedControllerService> controllerServices; + + @Schema(description = "The controller services") + public List<VersionedControllerService> getControllerServices() { + return controllerServices; + } + + public void setControllerServices(List<VersionedControllerService> controllerServices) { + this.controllerServices = controllerServices; + } + + @Schema(description = "The reporting tasks") + public List<VersionedReportingTask> getReportingTasks() { + return reportingTasks; + } + + public void setReportingTasks(List<VersionedReportingTask> reportingTasks) { + this.reportingTasks = reportingTasks; + } + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedResourceCardinality.java b/src/main/java/org/apache/nifi/flow/VersionedResourceCardinality.java new file mode 100644 index 0000000..3ce4495 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedResourceCardinality.java
@@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum VersionedResourceCardinality { + + /** + * Exactly one resource must be specified + */ + SINGLE, + + /** + * One or more resources may be supplied, as a comma-separated list + */ + MULTIPLE; + +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedResourceDefinition.java b/src/main/java/org/apache/nifi/flow/VersionedResourceDefinition.java new file mode 100644 index 0000000..a04e23b --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedResourceDefinition.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Set; + +public class VersionedResourceDefinition { + private VersionedResourceCardinality cardinality; + private Set<VersionedResourceType> resourceTypes; + + @Schema(description = "The cardinality of the resource") + public VersionedResourceCardinality getCardinality() { + return cardinality; + } + + public void setCardinality(final VersionedResourceCardinality cardinality) { + this.cardinality = cardinality; + } + + @Schema(description = "The types of resource that the Property Descriptor is allowed to reference") + public Set<VersionedResourceType> getResourceTypes() { + return resourceTypes; + } + + public void setResourceTypes(final Set<VersionedResourceType> resourceTypes) { + this.resourceTypes = resourceTypes; + } +}
diff --git a/src/main/java/org/apache/nifi/flow/VersionedResourceType.java b/src/main/java/org/apache/nifi/flow/VersionedResourceType.java new file mode 100644 index 0000000..7fb67d8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/VersionedResourceType.java
@@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flow; + +public enum VersionedResourceType { + /** + * Referenced Resource is a File on a local (or mounted) file system + */ + FILE, + + /** + * Referenced Resource is a directory on a local (or mounted) file system + */ + DIRECTORY, + + /** + * Referenced Resource is UTF-8 text, rather than an external entity + */ + TEXT, + + /** + * Referenced Resource is a URL that uses the HTTP, HTTPS, or file protocol + * (i.e., <code>http://...</code>, <code>https://...</code>, or <code>file:...</code>) + */ + URL; + +}
diff --git a/src/main/java/org/apache/nifi/flow/resource/ExternalResourceConflictResolutionStrategy.java b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceConflictResolutionStrategy.java new file mode 100644 index 0000000..85f781d --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceConflictResolutionStrategy.java
@@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow.resource; + +import java.io.File; + +/** + * Might be used by provider service in order to decide if an available resource should be acquired from the external provider. + */ +public interface ExternalResourceConflictResolutionStrategy { + /** + * @param targetDirectory The target directory. + * @param available Descriptor of the available resource. + * + * @return Returns true if the resource should be fetched from the provider. Returns false otherwise. + */ + boolean shouldBeFetched(File targetDirectory, ExternalResourceDescriptor available); +}
diff --git a/src/main/java/org/apache/nifi/flow/resource/ExternalResourceDescriptor.java b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceDescriptor.java new file mode 100644 index 0000000..ed832c8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceDescriptor.java
@@ -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. + */ +package org.apache.nifi.flow.resource; + +/** + * Describes an available resource might be fetched from the external source. + */ +public interface ExternalResourceDescriptor { + + /** + * @return The location of the resource, where the format depends on the actual provider implementation. + */ + String getLocation(); + + /** + * @return Returns the modification time of the original resource file using Unix timestamp format. + */ + long getLastModified(); +}
diff --git a/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProvider.java b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProvider.java new file mode 100644 index 0000000..7cf7c96 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProvider.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +/** + * Represents an external source where the resource files might be acquired from. These external resources might be + * various: database drivers, different kind of configurations and so on. + */ +public interface ExternalResourceProvider { + + /** + * Initializes the External Resource Provider based on the given set of properties. + * + * @param context Initialization context for the provider containing configuration properties + */ + void initialize(ExternalResourceProviderInitializationContext context); + + /** + * Performs a listing of all resources that are available. + * + * @return The result is a list of descriptors for the available resources. + * @throws IOException Thrown on failure to list resources + */ + Collection<ExternalResourceDescriptor> listResources() throws IOException; + + /** + * Fetches the resource determined by the descriptor. This usually happens based on {@code ExternalResourceDescriptor#getLocation} + * but implementations might differ. + * + * @param descriptor External Resource reference to be fetched + * @return Input Stream for external resource fetched + * @throws IOException Thrown on failure to fetch resources + */ + InputStream fetchExternalResource(ExternalResourceDescriptor descriptor) throws IOException; +}
diff --git a/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProviderInitializationContext.java b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProviderInitializationContext.java new file mode 100644 index 0000000..aafca70 --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/resource/ExternalResourceProviderInitializationContext.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow.resource; + +import javax.net.ssl.SSLContext; +import java.util.Map; +import java.util.function.Predicate; + +/** + * Contains necessary information for extensions of external resource provider functionality. + */ +public interface ExternalResourceProviderInitializationContext { + + /** + * @return Returns with the available properties. + */ + Map<String, String> getProperties(); + + /** + * @return A predicate, which might filter out unwanted files from the external source during listing. If no filtering + * necessary, then the predicate should return {@code true} for every call. + */ + default Predicate<ExternalResourceDescriptor> getFilter() { + return (descriptor) -> true; + } + + /** + * @return Returns an SSLContext. + */ + SSLContext getSSLContext(); +}
diff --git a/src/main/java/org/apache/nifi/flow/resource/ImmutableExternalResourceDescriptor.java b/src/main/java/org/apache/nifi/flow/resource/ImmutableExternalResourceDescriptor.java new file mode 100644 index 0000000..f6d3e1a --- /dev/null +++ b/src/main/java/org/apache/nifi/flow/resource/ImmutableExternalResourceDescriptor.java
@@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flow.resource; + +import java.util.Objects; + +public class ImmutableExternalResourceDescriptor implements ExternalResourceDescriptor { + private final String location; + private final long modifiedAt; + + public ImmutableExternalResourceDescriptor(final String location, final long modifiedAt) { + if (modifiedAt < 0) { + throw new IllegalArgumentException("The modification time cannot be negative"); + } + + this.location = Objects.requireNonNull(location); + this.modifiedAt = modifiedAt; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public long getLastModified() { + return modifiedAt; + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/AbstractAnalysisResult.java b/src/main/java/org/apache/nifi/flowanalysis/AbstractAnalysisResult.java new file mode 100644 index 0000000..e1eb61c --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/AbstractAnalysisResult.java
@@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import java.util.StringJoiner; + +/** + * Abstract class holding information about a {@link FlowAnalysisRule} violation. + */ +public abstract class AbstractAnalysisResult { + protected final String issueId; + protected final String message; + protected final String explanation; + + protected AbstractAnalysisResult(final String issueId, final String message, final String explanation) { + this.issueId = issueId; + this.message = message; + this.explanation = explanation; + } + + /** + * A rule-defined id that corresponds to a unique type of issue recognized by the rule. + * Newer analysis runs may produce a result with the same issueId in which case the old one will + * be overwritten (or recreated if it is the same in other aspects as well). + * However, if the previous result was disabled the new one will be disabled as well. + * + * @return Issue Identifier + */ + public String getIssueId() { + return issueId; + } + + /** + * Get message associated with results + * + * @return the rule violation message + */ + public String getMessage() { + return message; + } + + /** + * Get explanation associated with results + * + * @return a detailed explanation of the nature of the violation + */ + public String getExplanation() { + return explanation; + } + + @Override + public String toString() { + return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") + .add("issueId='" + issueId + "'") + .add("message='" + message + "'") + .add("explanation='" + explanation + "'") + .toString(); + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/AbstractFlowAnalysisRule.java b/src/main/java/org/apache/nifi/flowanalysis/AbstractFlowAnalysisRule.java new file mode 100644 index 0000000..e1b2ae9 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/AbstractFlowAnalysisRule.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.reporting.InitializationException; + +public abstract class AbstractFlowAnalysisRule extends AbstractConfigurableComponent implements FlowAnalysisRule { + private String identifier; + private String description; + + private ComponentLog logger; + + @Override + public void initialize(FlowAnalysisRuleInitializationContext context) throws InitializationException { + identifier = context.getIdentifier(); + description = getClass().getSimpleName() + "[id=" + identifier + "]"; + logger = context.getLogger(); + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public String toString() { + return description; + } + + /** + * @return the logger that has been provided to the component by the + * framework in its initialize method + */ + protected ComponentLog getLogger() { + return logger; + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/ComponentAnalysisResult.java b/src/main/java/org/apache/nifi/flowanalysis/ComponentAnalysisResult.java new file mode 100644 index 0000000..047c434 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/ComponentAnalysisResult.java
@@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +/** + * Holds information about a component violating a {@link FlowAnalysisRule} + */ +public class ComponentAnalysisResult extends AbstractAnalysisResult { + /** + * @param issueId A rule-defined id that corresponds to a unique type of issue recognized by the rule. + * Newer analysis runs may produce a result with the same issueId in which case the old one will + * be overwritten (or recreated if it is the same in other aspects as well). + * However, if the previous result was disabled the new one will be disabled as well. + * @param message A violation message + */ + public ComponentAnalysisResult(final String issueId, final String message) { + this(issueId, message, null); + } + + /** + * @param issueId A rule-defined id that corresponds to a unique type of issue recognized by the rule. + * Newer analysis runs may produce a result with the same issueId in which case the old one will + * be overwritten (or recreated if it is the same in other aspects as well). + * However, if the previous result was disabled the new one will be disabled as well. + * @param message A violation message + * @param explanation A detailed explanation of the violation + */ + public ComponentAnalysisResult(final String issueId, final String message, final String explanation) { + super(issueId, message, explanation); + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/EnforcementPolicy.java b/src/main/java/org/apache/nifi/flowanalysis/EnforcementPolicy.java new file mode 100644 index 0000000..c1cda44 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/EnforcementPolicy.java
@@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +public enum EnforcementPolicy { + /** + * Rules with this enforcement policy only warns about rule violations. + */ + WARN, + /** + * Rules with this enforcement policy also invalidate the corresponding components and fixing + * these problems are to be considered mandatory. + */ + ENFORCE; +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisContext.java b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisContext.java new file mode 100644 index 0000000..330cba8 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisContext.java
@@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.controller.VersionedControllerServiceLookup; + +import java.util.Optional; + +/** + * Used for accessing flow- or other analysis-related information + */ +public interface FlowAnalysisContext { + /** + * @return the {@link VersionedControllerServiceLookup} which can be used to obtain + * Versioned Controller Services during flow analysis + */ + VersionedControllerServiceLookup getVersionedControllerServiceLookup(); + + /** + * @return the currently configured maximum number of threads that can be + * used for executing processors at any given time. + */ + int getMaxTimerDrivenThreadCount(); + + /** + * @return <code>true</code> if this instance of NiFi is configured to be part of a cluster, <code>false</code> + * if this instance of NiFi is a standalone instance + */ + boolean isClustered(); + + /** + * @return an Optional with the ID of this node in the cluster, or empty if either this node is not clustered or the Node Identifier + * has not yet been established + */ + Optional<String> getClusterNodeIdentifier(); +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRule.java b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRule.java new file mode 100644 index 0000000..78aaf5a --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRule.java
@@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.flow.VersionedComponent; +import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.reporting.InitializationException; + +import java.util.Collection; +import java.util.Collections; + +/** + * A single rule that can analyze components or a flow (represented by a process group) + */ +public interface FlowAnalysisRule extends ConfigurableComponent { + /** + * Provides the Flow Analysis Rule with access to objects that may be of use + * throughout its lifecycle + * + * @param context see {@link FlowAnalysisRuleInitializationContext} + * @throws org.apache.nifi.reporting.InitializationException if unable to initialize + */ + void initialize(FlowAnalysisRuleInitializationContext context) throws InitializationException; + + /** + * Analyze a component provided by the framework. + * This is a callback method invoked by the framework. + * It should be expected that this method will be called with any and all available components. + * + * @param component the component to be analyzed + * @param context see {@link FlowAnalysisRuleContext} + * @return a collection of {@link ComponentAnalysisResult} as the result of the analysis of the given component + */ + default Collection<ComponentAnalysisResult> analyzeComponent(VersionedComponent component, FlowAnalysisRuleContext context) { + return Collections.emptySet(); + } + + /** + * Analyze a flow or a part of it, represented by a process group. + * This is a callback method invoked by the framework. + * It should be expected that this method will be called by the root process group and all of its child process groups. + * In case a flow analysis is requested for a particular process group this method will be called for all it's child + * process groups as well. + * + * @param processGroup the process group to be analyzed + * @param context see {@link FlowAnalysisRuleContext} + * @return a collection of {@link GroupAnalysisResult} as the result of the analysis. + * One {@link GroupAnalysisResult} in the collection can either refer to a component within the analyzed process group, + * to a child process group or the entirety of the process group + */ + default Collection<GroupAnalysisResult> analyzeProcessGroup(VersionedProcessGroup processGroup, FlowAnalysisRuleContext context) { + return Collections.emptySet(); + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleContext.java b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleContext.java new file mode 100644 index 0000000..06e905f --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleContext.java
@@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.context.PropertyContext; + +import java.util.Map; + +/** + * This interface provides a bridge between the NiFi Framework and a + * {@link FlowAnalysisRule}. This context allows a FlowAnalysisRule to access + * configuration supplied by the user. + */ +public interface FlowAnalysisRuleContext extends PropertyContext { + /** + * @return the name of the rule that is being triggered + */ + String getRuleName(); + + /** + * @return a Map of all known {@link PropertyDescriptor}s to their + * configured properties. This Map will contain a <code>null</code> for any + * Property that has not been configured by the user, even if the + * PropertyDescriptor has a default value + */ + Map<PropertyDescriptor, String> getProperties(); + + /** + * @return the StateManager that can be used to store and retrieve state for this component + */ + StateManager getStateManager(); + + /** + * @return a FlowAnalysisContext that can be used to access flow- or other analysis-related information + */ + FlowAnalysisContext getFlowAnalysisContext(); +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleInitializationContext.java b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleInitializationContext.java new file mode 100644 index 0000000..08c375e --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleInitializationContext.java
@@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.kerberos.KerberosContext; +import org.apache.nifi.logging.ComponentLog; + +/** + * Provides configuration information to a + * FlowAnalysisRule at the time of initialization + */ +public interface FlowAnalysisRuleInitializationContext extends KerberosContext { + + /** + * @return the identifier for the FlowAnalysisRule + */ + String getIdentifier(); + + /** + * @return a logger that can be used to log important events in a standard + * way and generate bulletins when appropriate + */ + ComponentLog getLogger(); + + /** + * @return the {@link NodeTypeProvider} which can be used to detect the node + * type of this NiFi instance. + * @since Apache NiFi 1.5.0 + */ + NodeTypeProvider getNodeTypeProvider(); +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleState.java b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleState.java new file mode 100644 index 0000000..79731ba --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/FlowAnalysisRuleState.java
@@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +public enum FlowAnalysisRuleState { + ENABLED, + DISABLED; +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/GroupAnalysisResult.java b/src/main/java/org/apache/nifi/flowanalysis/GroupAnalysisResult.java new file mode 100644 index 0000000..5366bf7 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/GroupAnalysisResult.java
@@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.flow.VersionedComponent; + +import java.util.Optional; +import java.util.StringJoiner; + +/** + * Holds information about a {@link FlowAnalysisRule} violation after analyzing (a part of) the flow, represented by a process group. + * One such analysis can result in multiple instances of this class. + */ +public class GroupAnalysisResult extends AbstractAnalysisResult { + private final Optional<VersionedComponent> component; + + private GroupAnalysisResult(final String issueId, final String message, final String explanation, final Optional<VersionedComponent> component) { + super(issueId, message, explanation); + this.component = component; + } + + /** + * @return the component this result corresponds to or empty if this result corresponds to the entirety of the process group that was analyzed + */ + public Optional<VersionedComponent> getComponent() { + return component; + } + + @Override + public String toString() { + return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") + .add("issueId='" + issueId + "'") + .add("message='" + message + "'") + .add("explanation='" + explanation + "'") + .add("component='" + component + "'") + .toString(); + } + + /** + * Build a new analysis result tied to the currently analyzed process group + * + * @param issueId A rule-defined id that corresponds to a unique type of issue recognized by the rule. + * Newer analysis runs may produce a result with the same issueId in which case the old one will + * be overwritten (or recreated if it is the same in other aspects as well). + * However, if the previous result was disabled the new one will be disabled as well. + * @param message A violation message + * @return a Builder for a new analysis result instance tied to the currently analyzed process group + */ + public static Builder forGroup(final String issueId, final String message) { + return new Builder(null, issueId, message); + } + + /** + * Build a new analysis result tied to a component. + * Note that the result will be scoped to the process group of the component and not the currently analyzed group. + * This means that even when a new analysis is run against that process group, this result will become obsolete. + * + * @param component The component that this result is tied to + * @param issueId A rule-defined id that corresponds to a unique type of issue recognized by the rule. + * Newer analysis runs may produce a result with the same issueId in which case the old one will + * be overwritten (or recreated if it is the same in other aspects as well). + * However, if the previous result was disabled the new one will be disabled as well. + * @param message A violation message + * @return a Builder for a new analysis result tied to a component + */ + public static Builder forComponent(final VersionedComponent component, final String issueId, final String message) { + return new Builder(component, issueId, message); + } + + public static class Builder { + private final VersionedComponent component; + private final String issueId; + private final String message; + private String explanation; + + private Builder(final VersionedComponent component, final String issueId, final String message) { + this.component = component; + this.issueId = issueId; + this.message = message; + } + + /** + * @param explanation A detailed explanation of the violation + * @return this Builder + */ + public Builder explanation(final String explanation) { + this.explanation = explanation; + return this; + } + + /** + * @return the flow analysis result + */ + public GroupAnalysisResult build() { + return new GroupAnalysisResult(issueId, message, explanation, Optional.ofNullable(component)); + } + } +}
diff --git a/src/main/java/org/apache/nifi/flowanalysis/VerifiableFlowAnalysisRule.java b/src/main/java/org/apache/nifi/flowanalysis/VerifiableFlowAnalysisRule.java new file mode 100644 index 0000000..aa6b08e --- /dev/null +++ b/src/main/java/org/apache/nifi/flowanalysis/VerifiableFlowAnalysisRule.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.flowanalysis; + +import org.apache.nifi.components.ConfigVerificationResult; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; + +import java.util.List; + +/** + * <p> + * Any Flow Analysis Rule that implements this interface will be provided the opportunity to verify + * a given configuration of the Flow Analysis Rule. This allows the Flow Analysis Rule to provide meaningful feedback + * to users when configuring the dataflow. + * </p> + * + * <p> + * Generally speaking, verification differs from validation in that validation is expected to be very + * quick and run often. If a Flow Analysis Rule is not valid, it cannot be started. However, verification may be + * more expensive or time-consuming to complete. For example, validation may ensure that a username is + * provided for connecting to an external service but should not perform any sort of network connection + * in order to verify that the username is accurate. Verification, on the other hand, may create resources + * such as network connections, may be more expensive to complete, and may be run only when a user invokes + * the action (though verification may later occur at other stages, such as when starting a component). + * </p> + * + * <p> + * Verification is allowed to be run only when a Flow Analysis Rule is fully stopped. I.e., it has no active threads + * and currently has a state of STOPPED. Therefore, any initialization logic that may need to be performed + * before the Flow Analysis Rule is triggered may also be required for verification. However, the framework is not responsible + * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such + * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog)} itself. + * The {@link #verify(ConfigurationContext, ComponentLog)} method will only be called if the configuration is valid according to the + * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid). + * </p> + */ +public interface VerifiableFlowAnalysisRule { + + /** + * Verifies that the configuration defined by the given ConfigurationContext is valid. + * + * @param context the Configuration Context that contains the necessary configuration + * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result + * in producing bulletins, which can be confusing. + * + * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed + */ + List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger); + +}
diff --git a/src/main/java/org/apache/nifi/flowfile/FlowFile.java b/src/main/java/org/apache/nifi/flowfile/FlowFile.java new file mode 100644 index 0000000..ef5f61e --- /dev/null +++ b/src/main/java/org/apache/nifi/flowfile/FlowFile.java
@@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowfile; + +import java.util.Map; + +/** + * <p> + * A flow file is a logical notion of an item in a flow with its associated + * attributes and identity which can be used as a reference for its actual + * content.</p> + * + * <b>All FlowFile implementations must be Immutable - Thread safe.</b> + */ +public interface FlowFile extends Comparable<FlowFile> { + + /** + * @return the unique identifier for this flow file which is guaranteed + * to be unique within a single running instance of nifi. This identifier + * should not be used for true universal unique type needs. For that consider + * using the attribute found in the flow file's attribute map keyed by + * CoreAttributes.UUID. + * For example, by calling getAttribute(CoreAttributes.UUID.key()). + */ + long getId(); + + /** + * @return the date at which the flow file entered the flow + */ + long getEntryDate(); + + /** + * @return the date at which the origin of this FlowFile entered the flow. + * For example, if FlowFile Z were derived from FlowFile Y and FlowFile Y + * was derived from FlowFile X, this date would be the entryDate (see + * {@link #getEntryDate()} of FlowFile X. + */ + long getLineageStartDate(); + + /** + * Returns a 64-bit integer that indicates the order in which the FlowFile was added to the + * flow with respect to other FlowFiles that have the same last lineage start date. + * I.e., if two FlowFiles return the same value for {@link #getLineageStartDate()}, the order + * in which those FlowFiles were added to the flow can be determined by looking at the result of + * this method. However, no guarantee is made by this method about the ordering of FlowFiles + * that have different values for the {@link #getLineageStartDate()} method. + * + * @return the index that can be used to compare two FlowFiles with the same lineage start date + * to understand the order in which the two FlowFiles were enqueued. + */ + long getLineageStartIndex(); + + /** + * @return the time at which the FlowFile was most recently added to a + * FlowFile queue, or {@code null} if the FlowFile has never been enqueued. + * This value will always be populated before it is passed to a + * FlowFilePrioritizer + */ + Long getLastQueueDate(); + + /** + * Returns a 64-bit integer that indicates the order in which the FlowFile was added to the + * FlowFile queue with respect to other FlowFiles that have the same last queue date. + * I.e., if two FlowFiles return the same value for {@link #getLastQueueDate()}, the order + * in which those FlowFiles were enqueued can be determined by looking at the result of + * this method. However, no guarantee is made by this method about the ordering of FlowFiles + * that have different values for the {@link #getLastQueueDate()} method. + * + * @return the index that can be used to compare two FlowFiles with the same last queue date + * to understand the order in which the two FlowFiles were enqueued. + */ + long getQueueDateIndex(); + + /** + * @return true if flow file is currently penalized; false otherwise; + */ + boolean isPenalized(); + + /** + * Obtains the attribute value for the given key + * + * @param key of the attribute + * @return value if found; null otherwise + */ + String getAttribute(String key); + + /** + * @return size of flow file contents in bytes + */ + long getSize(); + + /** + * @return an unmodifiable map of the flow file attributes + */ + Map<String, String> getAttributes(); + + class KeyValidator { + + private KeyValidator() { + + } + + public static String validateKey(final String key) { + // We used to validate the key by disallowing a handful of keywords, but this requirement no longer exists. + // Therefore this method simply verifies that the key is not empty. + if (key == null) { + throw new IllegalArgumentException("Invalid attribute key: null"); + } + if (key.trim().isEmpty()) { + throw new IllegalArgumentException("Invalid attribute key: <Empty String>"); + } + return key; + } + + public static boolean isValid(final String key) { + return key != null && !key.trim().isEmpty(); + } + } +}
diff --git a/src/main/java/org/apache/nifi/flowfile/attributes/CoreAttributes.java b/src/main/java/org/apache/nifi/flowfile/attributes/CoreAttributes.java new file mode 100644 index 0000000..65b41b2 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowfile/attributes/CoreAttributes.java
@@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowfile.attributes; + +public enum CoreAttributes implements FlowFileAttributeKey { + + /** + * The FlowFile's path indicates the relative directory to which a FlowFile belongs and does not contain the filename + */ + PATH("path"), + /** + * The FlowFile's absolute path indicates the absolute directory to which a FlowFile belongs and does not contain the filename + */ + ABSOLUTE_PATH("absolute.path"), + /** + * The filename of the FlowFile. The filename should not contain any directory structure. + */ + FILENAME("filename"), + /** + * A unique UUID assigned to this FlowFile + */ + UUID("uuid"), + /** + * A numeric value indicating the FlowFile priority + */ + PRIORITY("priority"), + /** + * The MIME Type of this FlowFile + */ + MIME_TYPE("mime.type"), + /** + * Specifies the reason that a FlowFile is being discarded + */ + DISCARD_REASON("discard.reason"), + /** + * Indicates an identifier other than the FlowFile's UUID that is known to refer to this FlowFile. + */ + ALTERNATE_IDENTIFIER("alternate.identifier"); + + private final String key; + + CoreAttributes(final String key) { + this.key = key; + } + + @Override + public String key() { + return key; + } + +}
diff --git a/src/main/java/org/apache/nifi/flowfile/attributes/FlowFileAttributeKey.java b/src/main/java/org/apache/nifi/flowfile/attributes/FlowFileAttributeKey.java new file mode 100644 index 0000000..9637631 --- /dev/null +++ b/src/main/java/org/apache/nifi/flowfile/attributes/FlowFileAttributeKey.java
@@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.flowfile.attributes; + +public interface FlowFileAttributeKey { + + String key(); +}
diff --git a/src/main/java/org/apache/nifi/kerberos/KerberosContext.java b/src/main/java/org/apache/nifi/kerberos/KerberosContext.java new file mode 100644 index 0000000..a30e7cd --- /dev/null +++ b/src/main/java/org/apache/nifi/kerberos/KerberosContext.java
@@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.kerberos; + +import java.io.File; + +public interface KerberosContext { + + /** + * The Kerberos service principal used by NiFi to communicate with the KDC + * in order to obtain tickets on behalf of NiFi. Typically of the form + * NIFI/fully.qualified.domain@REALM. + * + * @return the principal, or null if this NiFi instance is not configured + * with a NiFi Kerberos service principal + */ + String getKerberosServicePrincipal(); + + /** + * The File instance for the Kerberos service keytab. The service principal + * and service keytab will be used to communicate with the KDC to obtain + * tickets on behalf of NiFi. + * + * @return the File instance of the service keytab, or null if this NiFi + * instance is not configured with a NiFi Kerberos service keytab + */ + File getKerberosServiceKeytab(); + + /** + * The Kerberos configuration file (typically krb5.conf) that will be used + * by this JVM during all Kerberos operations. + * + * @return the File instance for the Kerberos configuration file, or null if + * this NiFi instance is not configured with a Kerberos configuration file + */ + File getKerberosConfigurationFile(); +}
diff --git a/src/main/java/org/apache/nifi/logging/ComponentLog.java b/src/main/java/org/apache/nifi/logging/ComponentLog.java new file mode 100644 index 0000000..db03fd7 --- /dev/null +++ b/src/main/java/org/apache/nifi/logging/ComponentLog.java
@@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.logging; + +/** + * <p> + * The ComponentLog provides a mechanism to ensure that all NiFi components are + * logging and reporting information in a consistent way. When messages are + * logged to the ComponentLog, each message has the following characteristics: + * </p> + * + * <ul> + * <li> + * The <code>toString()</code> of the component is automatically prepended to + * the message so that it is clear which component is providing the information. + * This is important, since a single component may have many different instances + * within the same NiFi instance. + * </li> + * <li> + * If the last value in an Object... argument that is passed to the logger is a + * Throwable, then the logged message will include a <code>toString()</code> of + * the Throwable; in addition, if the component's logger is set to DEBUG level + * via the logback configuration, the Stacktrace will also be logged. This + * provides a mechanism to easily enable stacktraces in the logs when they are + * desired without filling the logs with unneeded stack traces for messages that + * end up occurring often. + * </li> + * <li> + * Any message that is logged with a Severity level that meets or exceeds the + * configured Bulletin Level for that component will also cause a Bulletin to be + * generated, so that the message is visible in the UI, allowing Dataflow + * Managers to understand that a problem exists and what the issue is. + * </li> + * </ul> + */ +public interface ComponentLog { + + void warn(String msg, Throwable t); + + void warn(String msg, Object... os); + + void warn(String msg); + + default void warn(LogMessage logMessage) { + log(LogLevel.WARN, logMessage); + } + + void trace(String msg, Throwable t); + + void trace(String msg, Object... os); + + void trace(String msg); + + default void trace(LogMessage logMessage) { + log(LogLevel.TRACE, logMessage); + } + + boolean isWarnEnabled(); + + boolean isTraceEnabled(); + + boolean isInfoEnabled(); + + boolean isErrorEnabled(); + + boolean isDebugEnabled(); + + void info(String msg, Throwable t); + + void info(String msg, Object... os); + + void info(String msg); + + default void info(LogMessage logMessage) { + log(LogLevel.INFO, logMessage); + } + + String getName(); + + void error(String msg, Throwable t); + + void error(String msg, Object... os); + + void error(String msg); + + default void error(LogMessage logMessage) { + log(LogLevel.ERROR, logMessage); + } + + void debug(String msg, Throwable t); + + void debug(String msg, Object... os); + + void debug(String msg); + + default void debug(LogMessage logMessage) { + log(LogLevel.ERROR, logMessage); + } + + default void log(LogLevel level, String msg, Throwable t) { + switch (level) { + case DEBUG: + debug(msg, t); + break; + case ERROR: + case FATAL: + error(msg, t); + break; + case INFO: + info(msg, t); + break; + case TRACE: + trace(msg, t); + break; + case WARN: + default: + warn(msg, t); + break; + } + } + + default void log(LogLevel level, String msg, Object... os) { + switch (level) { + case DEBUG: + debug(msg, os); + break; + case ERROR: + case FATAL: + error(msg, os); + break; + case INFO: + info(msg, os); + break; + case TRACE: + trace(msg, os); + break; + case WARN: + default: + warn(msg, os); + break; + } + } + + default void log(LogLevel level, String msg) { + switch (level) { + case DEBUG: + debug(msg); + break; + case ERROR: + case FATAL: + error(msg); + break; + case INFO: + info(msg); + break; + case TRACE: + trace(msg); + break; + case WARN: + default: + warn(msg); + break; + } + } + + default void log(LogMessage message) { + switch (message.getLogLevel()) { + case DEBUG: + debug(message); + break; + case ERROR: + case FATAL: + error(message); + break; + case INFO: + info(message); + break; + case TRACE: + trace(message); + break; + case WARN: + default: + warn(message); + break; + } + } + + default void log(LogLevel level, LogMessage logMessage) { + String msg = logMessage.getMessage(); + Throwable t = logMessage.getThrowable(); + Object[] os = logMessage.getObjects(); + + if (os != null && t != null) { + Object[] ost = new Object[os.length + 1]; + System.arraycopy(os, 0, ost, 0, os.length); + ost[ost.length - 1] = t; + log(level, msg, ost); + } else if (os != null) { + log(level, msg, os); + } else if (t != null) { + log(level, msg, t); + } else { + log(level, msg); + } + } +}
diff --git a/src/main/java/org/apache/nifi/logging/LogLevel.java b/src/main/java/org/apache/nifi/logging/LogLevel.java new file mode 100644 index 0000000..c063bf8 --- /dev/null +++ b/src/main/java/org/apache/nifi/logging/LogLevel.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.logging; + +public enum LogLevel { + + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL, + NONE; +}
diff --git a/src/main/java/org/apache/nifi/logging/LogMessage.java b/src/main/java/org/apache/nifi/logging/LogMessage.java new file mode 100644 index 0000000..bae8154 --- /dev/null +++ b/src/main/java/org/apache/nifi/logging/LogMessage.java
@@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + +public class LogMessage { + + private final long time; + private final String message; + private final LogLevel logLevel; + private final Throwable throwable; + private final String flowFileUuid; + private final Object[] objects; + + private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; + private static final String TO_STRING_FORMAT = "%1$s %2$s - %3$s"; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT); + + public static class Builder { + + private final long time; + private final LogLevel logLevel; + private String message; + private Throwable throwable; + private String flowFileUuid; + private Object[] objects; + + public Builder(final long time, final LogLevel logLevel) { + this.time = time; + this.logLevel = logLevel; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder throwable(Throwable throwable) { + this.throwable = throwable; + return this; + } + + public Builder flowFileUuid(String flowFileUuid) { + this.flowFileUuid = flowFileUuid; + return this; + } + + public Builder objects(Object[] objects) { + this.objects = objects; + return this; + } + + public LogMessage createLogMessage() { + return new LogMessage(time, logLevel, message, throwable, flowFileUuid, objects); + } + } + + private LogMessage(final long time, final LogLevel logLevel, final String message, final Throwable throwable, final String flowFileUuid, final Object[] objects) { + this.logLevel = logLevel; + this.throwable = throwable; + this.message = message; + this.time = time; + this.flowFileUuid = flowFileUuid; + this.objects = objects; + } + + public long getTime() { + return time; + } + + public String getMessage() { + return message; + } + + public LogLevel getLogLevel() { + return logLevel; + } + + public Throwable getThrowable() { + return throwable; + } + + public String getFlowFileUuid() { + return flowFileUuid; + } + + public Object[] getObjects() { + return objects; + } + + @Override + public String toString() { + final String formattedTime = DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(time)); + + String formattedMsg = String.format(TO_STRING_FORMAT, formattedTime, logLevel.toString(), message); + if (throwable != null) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + formattedMsg += System.lineSeparator() + sw; + } + + return formattedMsg; + } +}
diff --git a/src/main/java/org/apache/nifi/migration/PropertyConfiguration.java b/src/main/java/org/apache/nifi/migration/PropertyConfiguration.java new file mode 100644 index 0000000..e6e2cc9 --- /dev/null +++ b/src/main/java/org/apache/nifi/migration/PropertyConfiguration.java
@@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.migration; + +import org.apache.nifi.components.PropertyDescriptor; + +import java.util.Map; +import java.util.Optional; + +public interface PropertyConfiguration { + + /** + * Renames an existing property, if it exists. If the property does not exist, this is a no-op + * + * @param propertyName the current name of the property + * @param newName the new name for the property + * @return <code>true</code> if the property was renamed, <code>false</code> if the property did not exist + */ + boolean renameProperty(String propertyName, String newName); + + /** + * Removes the property with the given name, if it exists. If the property does not exist, this is a no-op. + * @param propertyName the name of the property to remove + * @return <code>true</code> if the property was removed, <code>false</code> if the property did not exist + */ + boolean removeProperty(String propertyName); + + /** + * <p> + * Determines whether or not the configuration has an entry for the given property. This method will return <code>true</code> + * even if the value for the given property is <code>null</code>. A value of <code>false</code> will be returned only if the + * property name is not known to the configuration. This allows for disambiguation between a value that is configured to be + * <code>null</code> and a value that has not yet been configured. + * </p> + * <p> + * An idiom to determine if the property was explicitly set to <code>null</code> is as follows: + * </p> + * <pre><code> + * final boolean setToNull = configuration.hasProperty("MyProperty") && !configuration.isPropertySet("MyProperty"); + * </code></pre> + * + * @param propertyName the name of the property + * @return <code>true</code> if the property name is known to this configuration, <code>false</code> otherwise. + */ + boolean hasProperty(String propertyName); + + /** + * <p> + * Determines whether or not the configuration has an entry for the given property. This method will return <code>true</code> + * even if the value for the given property is <code>null</code>. A value of <code>false</code> will be returned only if the + * property identified by the descriptor is not known to the configuration. This allows for disambiguation between a value that is configured to be + * <code>null</code> and a value that has not yet been configured. + * </p> + * <p> + * An idiom to determine if the property was explicitly set to <code>null</code> is as follows: + * </p> + * <pre><code> + * final boolean setToNull = configuration.hasProperty(MY_PROPERTY) && !configuration.isSet(MY_PROPERTY); + * </code></pre> + * + * @param descriptor the property descriptor that identifies the property + * @return <code>true</code> if the property name is known to this configuration, <code>false</code> otherwise. + */ + default boolean hasProperty(PropertyDescriptor descriptor) { + return hasProperty(descriptor.getName()); + } + + /** + * Indicates whether or not the property with the given name has been set to a non-null value + * @param propertyName the name of the property + * @return <code>true</code> if the value has been set and is non-null. Returns <code>false</code> if the value is unset or is null + */ + boolean isPropertySet(String propertyName); + + /** + * Indicates whether or not the property identified by the given descriptor name has been set to a non-null value + * @param descriptor the descriptor that identifies the property + * @return <code>true</code> if the value has been set and is non-null. Returns <code>false</code> if the value is unset or is null + */ + default boolean isPropertySet(PropertyDescriptor descriptor) { + return isPropertySet(descriptor.getName()); + } + + /** + * Sets the value of the property with the given name and value + * @param propertyName the name of the property + * @param propertyValue the value to set + */ + void setProperty(String propertyName, String propertyValue); + + /** + * Sets the value of the property identified by the given descriptor's name to the given value + * @param descriptor the property descriptor that identifies the property + * @param propertyValue the value to set + */ + default void setProperty(PropertyDescriptor descriptor, String propertyValue) { + setProperty(descriptor.getName(), propertyValue); + } + + /** + * Returns an optional value representing the value of the property with the given name + * @param propertyName the name of the property + * @return an empty optional if the value is null or unset, else an Optional representing the configured value + */ + Optional<String> getPropertyValue(String propertyName); + + /** + * Returns an optional value representing the value of the property identified by the given descriptor + * @param descriptor the property descriptor that identifies the property + * @return an empty optional if the value is null or unset, else an Optional representing the configured value + */ + default Optional<String> getPropertyValue(PropertyDescriptor descriptor) { + return getPropertyValue(descriptor.getName()); + } + + /** + * Returns an optional value representing the "raw" value of the property with the given name. The "raw" value is + * the value before any parameters are substituted. + * + * @param propertyName the name of the property + * @return an empty optional if the value is null or unset, else an Optional representing the configured value + */ + Optional<String> getRawPropertyValue(String propertyName); + + /** + * Returns an optional value representing the "raw" value of the property identified by the given descriptor. The "raw" value is + * the value before any parameters are substituted. + * + * @param descriptor the descriptor that identifies the property + * @return an empty optional if the value is null or unset, else an Optional representing the configured value + */ + default Optional<String> getRawPropertyValue(PropertyDescriptor descriptor) { + return getRawPropertyValue(descriptor.getName()); + } + + /** + * Returns a map containing all of the configured properties + * @return a Map containing the names and values of all configured properties + */ + Map<String, String> getProperties(); + + /** + * Returns a map containing all of the raw property values + * + * @return a Map containing the names and values of all configured properties + */ + Map<String, String> getRawProperties(); + + /** + * <p> + * Creates a new Controller Service of the given type and configures it with the given property values. Note that if a Controller Service + * already exists within the same scope and with the same implementation and configuration, a new service may not be created and instead + * the existing service may be used. + * </p> + * + * <p> + * This allows for properties that were previously defined in the extension to be moved to a Controller Service. For example, + * consider a Processor that has "Username" and "Password" properties. In the next version of the Processor, we want to support + * multiple types of authentication, and we delegate the authentication to a Controller Service. Consider that the Controller Service + * implementation we wish to use has a classname of {@code org.apache.nifi.services.authentication.UsernamePassword}. We might then + * use this method as such: + * </p> + * + * <pre><code> + * // Create a new Controller Service of type org.apache.nifi.services.authentication.UsernamePassword whose Username and Password + * // properties match those currently configured for this Processor. + * final Map<String, String> serviceProperties = Map.of("Username", propertyConfiguration.getRawPropertyValue("Username"), + * "Password", propertyConfiguration.getRawPropertyValue("Password")); + * final String serviceId = propertyConfiguration.createControllerService("org.apache.nifi.services.authentication.UsernamePassword", serviceProperties); + * + * // Set our Authentication Service property to point to this new service. + * propertyConfiguration.setProperty(AUTHENTICATION_SERVICE, serviceId); + * + * // Remove the Username and Password properties from this Processor, since we are now going to use then Authentication Service. + * propertyConfiguration.removeProperty("Username"); + * propertyConfiguration.removeProperty("Password"); + * </code></pre> + * + * <p> + * Note the use of {@link #getRawPropertyValue(String)} here instead of {@link #getPropertyValue(String)}. Because we want to set + * the new Controller Service's value to the same value as is currently configured for the Processor's "Username" and "Password" properties, + * we use {@link #getRawPropertyValue(String)}. This ensures that if the Processor is configured using Parameters, those Parameter + * references are still held by the Controller Service. + * </p> + * + * <p> + * Also note that this method expects the classname of the implementation, not the classname of the interface. + * </p> + * + * @param implementationClassName the fully qualified classname of the Controller Service implementation + * @param serviceProperties the property values to configure the newly created Controller Service with + * @return an identifier for the Controller Service + */ + String createControllerService(String implementationClassName, Map<String, String> serviceProperties); +}
diff --git a/src/main/java/org/apache/nifi/migration/RelationshipConfiguration.java b/src/main/java/org/apache/nifi/migration/RelationshipConfiguration.java new file mode 100644 index 0000000..e43aa64 --- /dev/null +++ b/src/main/java/org/apache/nifi/migration/RelationshipConfiguration.java
@@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.migration; + +import org.apache.nifi.processor.Relationship; + +/** + * <p> + * RelationshipConfiguration can be used to convey how a Processor's configuration should be migrated from an old configuration + * to the latest configuration. This provides the ability to split a single Relationship into multiple, or to rename Relationships. + * </p> + * <p> + * Note that it does not provide for the ability to merge multiple Relationships into a single Relationship, as doing so would cause unexpected + * data duplication. Consider, for example, that a Processor has two Connections. The first has Relationship A while the second has Relationship B. + * If we allows A and B to be merged together into C, anything that gets transferred to C would now need to go to both of those Connections, creating + * data duplication. + * </p> + * <p> + * Additionally, there is no option to remove a Relationship. This is because if a Relationship is to be removed, it can be simply dropped from the Processor. + * Any existing connection that has that Relationship will be "ghosted" in the UI to show the user that it's no longer supported, but NiFi and the Processor + * will continue to behave as expected. + * </p> + */ +public interface RelationshipConfiguration { + + /** + * Changes the Relationship from the given older name to the updated name + * @param relationshipName the old name of the relationship + * @param newName the new name to use + * @return <code>true</code> if the Relationship is renamed, <code>false</code> if the given Relationship does not exist + * @throws IllegalStateException if both the relationship name and the new name already are defined as relationships + */ + boolean renameRelationship(String relationshipName, String newName); + + /** + * <p> + * Splits the given Relationship into multiple new relationships. Each connection that has the Relationship as a selected Relationship will be updated to + * have all of the new Relationships as selected relationships instead. If the existing Relationship is auto-terminated, all of the newly defined ones + * will be as well. If the existing Relationship is auto-retried, so will be the new Relationships. + * </p> + * + * <p> + * It is possible to split an existing relationship into the same relationship and additional relationships. For example, it is + * valid to call this method as: + * </p> + * <pre><code> + * relationshipConfiguration.splitRelationship("A", "A", "B", "C"); + * </code></pre> + * + * <p> + * In order to split the "A" relationship into three relationships: "A", "B", and "C". However, upon restart, NiFi will have already split the "A" + * relationship into three. So relationships "B" and "C" will already exist, resulting in an {@code IllegalStateException}. Therefore, if splitting + * a relationship into multiple that include the original relationship, it is important to guard against this by checking of the new relationships + * exist first: + * </p> + * + * <pre><code> + * if (!relationshipConfiguration.hasRelationship("B")) { + * relationshipConfiguration.splitRelationship("A", "A", "B", "C"); + * } + * </code></pre> + * + * <p> + * This ensures that we do not attempt to split relationship "A" if it has already been done. + * </p> + * + * @param relationshipName the name of the existing relationship + * @param newRelationshipName the first of the new relationship names + * @param additionalRelationshipNames additional names for the new relationship + * @return <code>true</code> if the Relationship is split, <code>false</code> if the given Relationship does not exist + * @throws IllegalStateException if the given relationship name exists and one of the given new relationships already exists + */ + boolean splitRelationship(String relationshipName, String newRelationshipName, String... additionalRelationshipNames); + + /** + * Indicates whether or not the relationship with the given name exists in the configuration + * @param relationshipName the name of the relationship + * @return <code>true</code> if the relationship exists, <code>false</code> if the relationship is not known + */ + boolean hasRelationship(String relationshipName); + + /** + * Indicates whether or not the relationship with the given name exists in the configuration + * @param relationship the relationship to check + * @return <code>true</code> if the relationship exists, <code>false</code> if the relationship is not known + */ + default boolean hasRelationship(Relationship relationship) { + return hasRelationship(relationship.getName()); + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/AbstractParameterProvider.java b/src/main/java/org/apache/nifi/parameter/AbstractParameterProvider.java new file mode 100644 index 0000000..bcdeb4e --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/AbstractParameterProvider.java
@@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.reporting.InitializationException; + +public abstract class AbstractParameterProvider extends AbstractConfigurableComponent implements ParameterProvider { + private String identifier; + private String name; + private ComponentLog logger; + private NodeTypeProvider nodeTypeProvider; + + @Override + public final void initialize(final ParameterProviderInitializationContext config) throws InitializationException { + identifier = config.getIdentifier(); + logger = config.getLogger(); + name = config.getName(); + nodeTypeProvider = config.getNodeTypeProvider(); + init(config); + } + + /** + * @return the identifier of this Parameter Provider + */ + @Override + public String getIdentifier() { + return identifier; + } + + /** + * @return the {@link NodeTypeProvider} that was passed to the + * {@link #initialize(ParameterProviderInitializationContext)} method + */ + protected final NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + /** + * @return the name of this Parameter Provider + */ + protected String getName() { + return name; + } + + /** + * @return the logger that has been provided to the component by the + * framework in its initialize method + */ + protected ComponentLog getLogger() { + return logger; + } + + /** + * Provides a mechanism by which subclasses can perform initialization of + * the Parameter Provider before its parameters are fetched + * + * @param config context + * @throws InitializationException if failure to init + */ + protected void init(final ParameterProviderInitializationContext config) throws InitializationException { + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/Parameter.java b/src/main/java/org/apache/nifi/parameter/Parameter.java new file mode 100644 index 0000000..ff3030f --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/Parameter.java
@@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import org.apache.nifi.asset.Asset; + +import java.io.File; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class Parameter { + private final ParameterDescriptor descriptor; + private final String value; + private final String parameterContextId; + private final boolean provided; + private final List<Asset> referencedAssets; + + private Parameter(final Builder builder) { + this.descriptor = new ParameterDescriptor.Builder() + .name(builder.name) + .description(builder.description) + .sensitive(builder.sensitive) + .build(); + + this.parameterContextId = builder.parameterContextId; + this.provided = builder.provided; + + this.referencedAssets = builder.referencedAssets; + if (this.referencedAssets == null || this.referencedAssets.isEmpty()) { + this.value = builder.value; + } else { + this.value = referencedAssets.stream() + .map(Asset::getFile) + .map(File::getAbsolutePath) + .collect(Collectors.joining(",")); + } + } + + public ParameterDescriptor getDescriptor() { + return descriptor; + } + + public String getValue() { + return value; + } + + public List<Asset> getReferencedAssets() { + return referencedAssets; + } + + public String getParameterContextId() { + return parameterContextId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Parameter parameter = (Parameter) o; + return Objects.equals(descriptor, parameter.descriptor) + && Objects.equals(value, parameter.value) + && Objects.equals(parameterContextId, parameter.parameterContextId) + && Objects.equals(referencedAssets, parameter.referencedAssets); + } + + @Override + public int hashCode() { + return Objects.hash(descriptor, value); + } + + /** + * + * @return True if this parameter is provided by a ParameterProvider. + */ + public boolean isProvided() { + return provided; + } + + public static class Builder { + private String name; + private String description; + private boolean sensitive; + private String value; + private String parameterContextId; + private boolean provided; + private List<Asset> referencedAssets = List.of(); + + public Builder fromParameter(final Parameter parameter) { + descriptor(parameter.getDescriptor()); + this.parameterContextId = parameter.getParameterContextId(); + this.provided = parameter.isProvided(); + this.referencedAssets = parameter.getReferencedAssets() == null ? List.of() : parameter.getReferencedAssets(); + if (this.referencedAssets.isEmpty()) { + this.value = parameter.getValue(); + } + + return this; + } + + public Builder descriptor(final ParameterDescriptor descriptor) { + this.name = descriptor.getName(); + this.description = descriptor.getDescription(); + this.sensitive = descriptor.isSensitive(); + return this; + } + + public Builder name(final String name) { + this.name = name; + return this; + } + + public Builder description(final String description) { + this.description = description; + return this; + } + + public Builder sensitive(final boolean sensitive) { + this.sensitive = sensitive; + return this; + } + + public Builder value(final String value) { + this.value = value; + this.referencedAssets = List.of(); + return this; + } + + public Builder parameterContextId(final String parameterContextId) { + this.parameterContextId = parameterContextId; + return this; + } + + public Builder provided(final Boolean provided) { + this.provided = provided != null && provided; + return this; + } + + public Builder referencedAssets(final List<Asset> referencedAssets) { + this.referencedAssets = referencedAssets == null ? List.of() : referencedAssets; + if (!this.referencedAssets.isEmpty()) { + this.value = null; + } + + return this; + } + + public Parameter build() { + if (name == null) { + throw new IllegalStateException("Name or Descriptor is required"); + } + if (value != null && referencedAssets != null && !referencedAssets.isEmpty()) { + throw new IllegalStateException("A Parameter's value or referenced assets may be set but not both"); + } + + return new Parameter(this); + } + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterDescriptor.java b/src/main/java/org/apache/nifi/parameter/ParameterDescriptor.java new file mode 100644 index 0000000..5f6e2f4 --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterDescriptor.java
@@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import java.util.Objects; + +/** + * An immutable class that holds information about a Parameter. Parameters are constructed through use of the {@link ParameterDescriptor.Builder} class. + */ +public class ParameterDescriptor { + private final String name; + private final String description; + private final boolean sensitive; + + private ParameterDescriptor(final Builder builder) { + this.name = builder.name; + this.description = builder.description; + this.sensitive = builder.sensitive; + } + + /** + * @return the name of the parameter + */ + public String getName() { + return name; + } + + /** + * @return a description of the parameter + */ + public String getDescription() { + return description; + } + + /** + * @return whether or not the parameter is considered sensitive. + */ + public boolean isSensitive() { + return sensitive; + } + + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + + final ParameterDescriptor other = (ParameterDescriptor) o; + return Objects.equals(this.name, other.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + + public static class Builder { + private String name; + private String description; + private boolean sensitive; + + public Builder name(final String name) { + Objects.requireNonNull(name); + + this.name = name.trim(); + return this; + } + + public Builder description(final String description) { + this.description = description == null ? null : description.trim(); + return this; + } + + public Builder sensitive(final boolean sensitive) { + this.sensitive = sensitive; + return this; + } + + public Builder from(final ParameterDescriptor descriptor) { + name(descriptor.getName()); + description(descriptor.getDescription()); + sensitive(descriptor.isSensitive()); + return this; + } + + public ParameterDescriptor build() { + if (name == null) { + throw new IllegalStateException("Must specify Parameter Name"); + } + + return new ParameterDescriptor(this); + } + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterGroup.java b/src/main/java/org/apache/nifi/parameter/ParameterGroup.java new file mode 100644 index 0000000..c393387 --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterGroup.java
@@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import java.util.Collections; +import java.util.List; + +/** + * Encapsulates a named group of externally fetched parameters that can be provided to referencing Parameter Contexts. + */ +public class ParameterGroup { + + private final String groupName; + + private final List<Parameter> parameters; + + /** + * Creates a named parameter group. + * @param groupName The parameter group name + * @param parameters A list of parameters + */ + public ParameterGroup(final String groupName, final List<Parameter> parameters) { + this.groupName = groupName; + this.parameters = Collections.unmodifiableList(parameters); + } + + /** + * @return The group name + */ + public String getGroupName() { + return groupName; + } + + /** + * @return The provided parameters + */ + public List<Parameter> getParameters() { + return parameters; + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterGroupConfiguration.java b/src/main/java/org/apache/nifi/parameter/ParameterGroupConfiguration.java new file mode 100644 index 0000000..c519327 --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterGroupConfiguration.java
@@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * User-provided configuration for a group of parameters fetched from a ParameterProvider. + */ +public class ParameterGroupConfiguration implements Comparable<ParameterGroupConfiguration> { + + private final String groupName; + + private final String parameterContextName; + + private final Map<String, ParameterSensitivity> parameterSensitivities; + + private final Boolean isSynchronized; + + /** + * Creates a named group of parameter names. + * @param groupName The parameter group name + * @param parameterContextName The parameter context name to which parameters will be applied + * @param parameterSensitivities A map from parameter name to desired sensitivity. Any parameter not included in this map will not be included + * when applied to the parameter context. + * @param isSynchronized If true, indicates that a ParameterContext should be created if not already existing, or updated if existing + */ + public ParameterGroupConfiguration(final String groupName, final String parameterContextName, final Map<String, ParameterSensitivity> parameterSensitivities, + final Boolean isSynchronized) { + this.groupName = Objects.requireNonNull(groupName, "Parameter group name is required"); + this.parameterContextName = Optional.ofNullable(parameterContextName).orElse(groupName); + this.parameterSensitivities = Collections.unmodifiableMap(Objects.requireNonNull(parameterSensitivities, "Parameter sensitivity map is required")); + this.isSynchronized = isSynchronized; + } + + /** + * @return The external parameter group name + */ + public String getGroupName() { + return groupName; + } + + /** + * @return The name of the ParameterContext that maps to this group + */ + public String getParameterContextName() { + return parameterContextName; + } + + /** + * @return A map from parameter name to desired sensitivity. If the sensitivity is null, this indicates that the parameter + * has not yet been configured by the user. + */ + public Map<String, ParameterSensitivity> getParameterSensitivities() { + return parameterSensitivities; + } + + /** + * @return True if this group should be synchronized with a parameter context. If null, this indicates that + * it has not yet been configured by the user. + */ + public Boolean isSynchronized() { + return isSynchronized; + } + + @Override + public int compareTo(final ParameterGroupConfiguration other) { + if (other == null) { + return -1; + } + + final String groupName = getGroupName(); + final String otherGroupName = other.getGroupName(); + + if (groupName == null) { + return otherGroupName == null ? 0 : -1; + } + if (otherGroupName == null) { + return 1; + } + return groupName.compareTo(otherGroupName); + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterProvider.java b/src/main/java/org/apache/nifi/parameter/ParameterProvider.java new file mode 100644 index 0000000..7179bfb --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterProvider.java
@@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.reporting.InitializationException; + +import java.io.IOException; +import java.util.List; + +/** + * Defines a provider that is responsible for fetching from an external source Parameters with + * which a ParameterContext can be populated. + * + * <p> + * <code>ParameterProvider</code>s are discovered following Java's + * <code>ServiceLoader</code> pattern. As a result, all implementations must + * follow these rules: + * + * <ul> + * <li>The implementation must implement this interface.</li> + * <li>The implementation must have a file named + * org.apache.nifi.parameter.ParameterProvider located within the jar's + * <code>META-INF/services</code> directory. This file contains a list of + * fully-qualified class names of all <code>ParameterProvider</code>s in the jar, + * one-per-line. + * <li>The implementation must support a default constructor.</li> + * </ul> + * + * <p> + * All implementations of this interface must be thread-safe. + * </p> + * + * <p> + * Parameter Providers may choose to annotate a method with the + * {@link OnConfigurationRestored @OnConfigurationRestored} annotation. If this is done, that method + * will be invoked after all properties have been set for the ParameterProvider and + * before its parameters are fetched. + * </p> + */ +public interface ParameterProvider extends ConfigurableComponent { + + /** + * Provides the Parameter Provider with access to objects that may be of use + * throughout the life of the provider + * + * @param config of initialization context + * @throws org.apache.nifi.reporting.InitializationException if unable to init + */ + void initialize(ParameterProviderInitializationContext config) throws InitializationException; + + /** + * Fetches named groups of parameters from an external source. + * + * Any referencing Parameter Context will only receive the Parameters from a group if the Parameter Context name matches + * the group name (ignoring case) and the reference sensitivity matches the group sensitivity. + * + * If group name is null, all referencing Parameter Contexts will receive the Parameters in that group, + * regardless of their name. + * + * If more than one ProvidedParameterGroup matches a given ParameterContext, all parameters from these groups will be + * applied. However, if any parameters among these matching groups have the same name but different value, the framework + * will throw a <code>RuntimeException</code> + * + * @param context The <code>ConfigurationContext</code>for the provider + * @return A list of fetched Parameter groups. The framework will set the sensitivity appropriately based on how the ParameterProvider + * is referenced in a ParameterContext. + * @throws IOException if there is an I/O problem while fetching the Parameters + */ + List<ParameterGroup> fetchParameters(ConfigurationContext context) throws IOException; +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterProviderInitializationContext.java b/src/main/java/org/apache/nifi/parameter/ParameterProviderInitializationContext.java new file mode 100644 index 0000000..ebf813c --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterProviderInitializationContext.java
@@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.kerberos.KerberosContext; +import org.apache.nifi.logging.ComponentLog; + +public interface ParameterProviderInitializationContext extends KerberosContext { + + /** + * @return the identifier associated with the {@link ParameterProvider} with + * which this context is associated + */ + String getIdentifier(); + + /** + * @return the configured name for this ParameterProvider + */ + String getName(); + + /** + * @return a logger that can be used to log important events in a standard + * way and generate bulletins when appropriate + */ + ComponentLog getLogger(); + + /** + * @return the {@link NodeTypeProvider} which can be used to detect the node + * type of this NiFi instance. + */ + NodeTypeProvider getNodeTypeProvider(); +}
diff --git a/src/main/java/org/apache/nifi/parameter/ParameterSensitivity.java b/src/main/java/org/apache/nifi/parameter/ParameterSensitivity.java new file mode 100644 index 0000000..6d3c523 --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/ParameterSensitivity.java
@@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.parameter; + +/** + * Indicates the sensitivity of a parameter. + */ +public enum ParameterSensitivity { + SENSITIVE("Sensitive"), + NON_SENSITIVE("Non-Sensitive"); + + private String name; + + ParameterSensitivity(final String name) { + this.name = name; + } + + public String getName() { + return name; + } +}
diff --git a/src/main/java/org/apache/nifi/parameter/VerifiableParameterProvider.java b/src/main/java/org/apache/nifi/parameter/VerifiableParameterProvider.java new file mode 100644 index 0000000..8c7db57 --- /dev/null +++ b/src/main/java/org/apache/nifi/parameter/VerifiableParameterProvider.java
@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.parameter; + +import org.apache.nifi.components.ConfigVerificationResult; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; + +import java.util.List; + +/** + * <p> + * Any Parameter Provider that implements this interface will be provided the opportunity to verify + * a given configuration of the Parameter Provider. This allows the Parameter Provider to provide meaningful feedback + * to users when configuring the dataflow. + * </p> + * + * <p> + * Generally speaking, verification differs from validation in that validation is expected to be very + * quick and run often. If a Parameter Provider is not valid, its parameters cannot be fetched. However, verification may be + * more expensive or time-consuming to complete. For example, validation may ensure that a username is + * provided for connecting to an external service but should not perform any sort of network connection + * in order to verify that the username is accurate. Verification, on the other hand, may create resources + * such as network connections, may be more expensive to complete, and may be run only when a user invokes + * the action (though verification may later occur at other stages, such as when fetching the parameters). + * </p> + * + * <p> + * The framework is not responsible for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such + * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog)} itself. + * The {@link #verify(ConfigurationContext, ComponentLog)} method will only be called if the configuration is valid according to the + * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid). + * </p> + */ +public interface VerifiableParameterProvider { + + /** + * Verifies that the configuration defined by the given ConfigurationContext is valid. + * + * @param context the Configuration Context that contains the necessary configuration + * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result + * in producing bulletins, which can be confusing. + * + * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed + */ + List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger); + +}
diff --git a/src/main/java/org/apache/nifi/processor/AbstractProcessor.java b/src/main/java/org/apache/nifi/processor/AbstractProcessor.java new file mode 100644 index 0000000..21690d4 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/AbstractProcessor.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import org.apache.nifi.processor.exception.ProcessException; + +public abstract class AbstractProcessor extends AbstractSessionFactoryProcessor { + + @Override + public final void onTrigger(final ProcessContext context, final ProcessSessionFactory sessionFactory) throws ProcessException { + final ProcessSession session = sessionFactory.createSession(); + try { + onTrigger(context, session); + session.commitAsync(); + } catch (final Throwable t) { + session.rollback(true); + throw t; + } + } + + public abstract void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException; +}
diff --git a/src/main/java/org/apache/nifi/processor/AbstractSessionFactoryProcessor.java b/src/main/java/org/apache/nifi/processor/AbstractSessionFactoryProcessor.java new file mode 100644 index 0000000..2394805 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/AbstractSessionFactoryProcessor.java
@@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import java.util.Collections; +import java.util.Set; +import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored; +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.annotation.lifecycle.OnUnscheduled; +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; + +/** + * <p> + * Provides a standard partial implementation of a {@link Processor}. This + * implementation provides default behavior and various convenience hooks for + * processing.</p> + * + * <p> + * Implementation/Design note: This class follows the open/closed principle in a + * fairly strict manner meaning that subclasses are free to customize behavior + * in specifically designed points exclusively. If greater flexibility is + * necessary then it is still possible to simply implement the {@link Processor} + * interface.</p> + * + * <p> + * Thread safe</p> + * + */ +public abstract class AbstractSessionFactoryProcessor extends AbstractConfigurableComponent implements Processor { + + private String identifier; + private ComponentLog logger; + private volatile boolean scheduled = false; + private volatile boolean configurationRestored = false; + private ControllerServiceLookup serviceLookup; + private NodeTypeProvider nodeTypeProvider; + private String description; + + @Override + public final void initialize(final ProcessorInitializationContext context) { + identifier = context.getIdentifier(); + logger = context.getLogger(); + serviceLookup = context.getControllerServiceLookup(); + nodeTypeProvider = context.getNodeTypeProvider(); + init(context); + + description = getClass().getSimpleName() + "[id=" + identifier + "]"; + } + + /** + * @return the {@link ControllerServiceLookup} that was passed to the + * {@link #init(ProcessorInitializationContext)} method + */ + protected final ControllerServiceLookup getControllerServiceLookup() { + return serviceLookup; + } + + /** + * @return the {@link NodeTypeProvider} that was passed to the + * {@link #init(ProcessorInitializationContext)} method + */ + protected final NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + @Override + public Set<Relationship> getRelationships() { + return Collections.emptySet(); + } + + protected final ComponentLog getLogger() { + return logger; + } + + /** + * Provides subclasses the ability to perform initialization logic + * + * @param context in which to perform initialization + */ + protected void init(final ProcessorInitializationContext context) { + // Provided for subclasses to override + } + + /** + * @return <code>true</code> if the processor is scheduled to run, + * <code>false</code> otherwise + */ + protected final boolean isScheduled() { + return scheduled; + } + + @OnScheduled + public final void updateScheduledTrue() { + scheduled = true; + } + + @OnUnscheduled + public final void updateScheduledFalse() { + scheduled = false; + } + + @OnConfigurationRestored + public final void updateConfiguredRestoredTrue() { + configurationRestored = true; + } + + /** + * Returns a boolean indicating whether or not the configuration of the Processor has already been restored. + * See the {@link OnConfigurationRestored} annotation for more information about what it means for the configuration + * to be restored. + * + * @return <code>true</code> if configuration has been restored, <code>false</code> otherwise. + */ + protected boolean isConfigurationRestored() { + return configurationRestored; + } + + @Override + public final String getIdentifier() { + return identifier; + } + + @Override + public String toString() { + return description; + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/DataUnit.java b/src/main/java/org/apache/nifi/processor/DataUnit.java new file mode 100644 index 0000000..4980b97 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/DataUnit.java
@@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public enum DataUnit { + + /** + * Bytes + */ + B { + @Override + public double toB(double value) { + return value; + } + + @Override + public double toKB(double value) { + return value / POWERS[1]; + } + + @Override + public double toMB(double value) { + return value / POWERS[2]; + } + + @Override + public double toGB(double value) { + return value / POWERS[3]; + } + + @Override + public double toTB(double value) { + return value / POWERS[4]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toB(sourceSize); + } + }, + /** + * Kilobytes + */ + KB { + @Override + public double toB(double value) { + return value * POWERS[1]; + } + + @Override + public double toKB(double value) { + return value; + } + + @Override + public double toMB(double value) { + return value / POWERS[1]; + } + + @Override + public double toGB(double value) { + return value / POWERS[2]; + } + + @Override + public double toTB(double value) { + return value / POWERS[3]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toKB(sourceSize); + } + }, + /** + * Megabytes + */ + MB { + @Override + public double toB(double value) { + return value * POWERS[2]; + } + + @Override + public double toKB(double value) { + return value * POWERS[1]; + } + + @Override + public double toMB(double value) { + return value; + } + + @Override + public double toGB(double value) { + return value / POWERS[1]; + } + + @Override + public double toTB(double value) { + return value / POWERS[2]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toMB(sourceSize); + } + }, + /** + * Gigabytes + */ + GB { + @Override + public double toB(double value) { + return value * POWERS[3]; + } + + @Override + public double toKB(double value) { + return value * POWERS[2]; + } + + @Override + public double toMB(double value) { + return value * POWERS[1]; + } + + @Override + public double toGB(double value) { + return value; + } + + @Override + public double toTB(double value) { + return value / POWERS[1]; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toGB(sourceSize); + } + }, + /** + * Terabytes + */ + TB { + @Override + public double toB(double value) { + return value * POWERS[4]; + } + + @Override + public double toKB(double value) { + return value * POWERS[3]; + } + + @Override + public double toMB(double value) { + return value * POWERS[2]; + } + + @Override + public double toGB(double value) { + return value * POWERS[1]; + } + + @Override + public double toTB(double value) { + return value; + } + + @Override + public double convert(double sourceSize, DataUnit sourceUnit) { + return sourceUnit.toTB(sourceSize); + } + }; + + public double convert(final double sourceSize, final DataUnit sourceUnit) { + throw new AbstractMethodError(); + } + + public double toB(double size) { + throw new AbstractMethodError(); + } + + public double toKB(double size) { + throw new AbstractMethodError(); + } + + public double toMB(double size) { + throw new AbstractMethodError(); + } + + public double toGB(double size) { + throw new AbstractMethodError(); + } + + public double toTB(double size) { + throw new AbstractMethodError(); + } + + public static final double[] POWERS = {1, + 1024D, + 1024 * 1024D, + 1024 * 1024 * 1024D, + 1024 * 1024 * 1024 * 1024D}; + + public static final String DATA_SIZE_REGEX = "(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)"; + public static final Pattern DATA_SIZE_PATTERN = Pattern.compile(DATA_SIZE_REGEX); + + public static Double parseDataSize(final String value, final DataUnit units) { + if (value == null) { + return null; + } + + final Matcher matcher = DATA_SIZE_PATTERN.matcher(value.toUpperCase()); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid data size: " + value); + } + + final String sizeValue = matcher.group(1); + final String unitValue = matcher.group(2); + + final DataUnit sourceUnit = DataUnit.valueOf(unitValue); + final double size = Double.parseDouble(sizeValue); + return units.convert(size, sourceUnit); + } +}
diff --git a/src/main/java/org/apache/nifi/processor/FlowFileFilter.java b/src/main/java/org/apache/nifi/processor/FlowFileFilter.java new file mode 100644 index 0000000..18501cb --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/FlowFileFilter.java
@@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import org.apache.nifi.flowfile.FlowFile; + +/** + * <p> + * FlowFileFilter provides a mechanism for selectively choosing which FlowFiles + * to obtain from a Processor's incoming connections. + * </p> + * + * <p> + * Implementations of this interface need not be thread-safe. + * </p> + */ +public interface FlowFileFilter { + + /** + * Indicates whether or not the given FlowFile should be selected and + * whether or not the Processor is interested in filtering additional + * FlowFiles + * + * @param flowFile to apply the filter to + * @return true if the given FlowFile should be selected and + * if Processor is interested in filtering additional + * FlowFiles + */ + FlowFileFilterResult filter(FlowFile flowFile); + + /** + * Provides a result type to indicate whether or not a FlowFile should be + * selected + */ + enum FlowFileFilterResult { + + /** + * Indicates that a FlowFile should be returned to the Processor to be + * processed and that additional FlowFiles should be processed by this + * filter. + */ + ACCEPT_AND_CONTINUE(true, true), + /** + * Indicates that a FlowFile should be returned to the Processor to be + * processed and that this is the last FlowFile that should be processed + * by this filter. + */ + ACCEPT_AND_TERMINATE(false, true), + /** + * Indicates that a FlowFile should not be processed by the Processor at + * this time but that additional FlowFiles should be processed by this + * filter. + */ + REJECT_AND_CONTINUE(true, false), + /** + * Indicates that a FlowFile should not be processed by the Processor at + * this time and that no additional FlowFiles should be processed + * either. + */ + REJECT_AND_TERMINATE(false, false); + + private final boolean continueProcessing; + private final boolean accept; + + FlowFileFilterResult(final boolean continueProcessing, final boolean accept) { + this.continueProcessing = continueProcessing; + this.accept = accept; + } + + public boolean isAccept() { + return accept; + } + + public boolean isContinue() { + return continueProcessing; + } + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/ProcessContext.java b/src/main/java/org/apache/nifi/processor/ProcessContext.java new file mode 100644 index 0000000..6b15a1a --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/ProcessContext.java
@@ -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. + */ +package org.apache.nifi.processor; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.context.ClusterContext; +import org.apache.nifi.context.PropertyContext; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.scheduling.ExecutionNode; + +import java.util.Map; +import java.util.Set; + +/** + * <p> + * Provides a bridge between a Processor and the NiFi Framework + * </p> + * + * <p> + * <b>Note: </b>Implementations of this interface are NOT necessarily + * thread-safe. + * </p> + */ +public interface ProcessContext extends PropertyContext, ClusterContext { + + /** + * Retrieves the current value set for the given descriptor, if a value is + * set - else uses the descriptor to determine the appropriate default value + * + * @param propertyName of the property to lookup the value for + * @return property value as retrieved by property name + */ + PropertyValue getProperty(String propertyName); + + /** + * Creates and returns a {@link PropertyValue} object that can be used for + * evaluating the value of the given String + * + * @param rawValue the raw input before any property evaluation has occurred + * @return a {@link PropertyValue} object that can be used for + * evaluating the value of the given String + */ + PropertyValue newPropertyValue(String rawValue); + + /** + * <p> + * Causes the Processor not to be scheduled for some pre-configured amount + * of time. The duration of time for which the processor will not be + * scheduled is configured in the same manner as the processor's scheduling + * period. + * </p> + * + * <p> + * <b>Note: </b>This is NOT a blocking call and does not suspend execution + * of the current thread. + * </p> + */ + void yield(); + + /** + * @return the maximum number of threads that may be executing this + * processor's code at any given time + */ + int getMaxConcurrentTasks(); + + /** + * @return the Nodes where this processor will be scheduled to run. + */ + ExecutionNode getExecutionNode(); + + /** + * @return the annotation data configured for this processor + */ + String getAnnotationData(); + + /** + * @return a Map of all PropertyDescriptors to their configured values. This + * Map may or may not be modifiable, but modifying its values will not + * change the values of the processor's properties + */ + Map<PropertyDescriptor, String> getProperties(); + + /** + * @return a {@code ControllerServiceLookup} that can be used to obtain a + * Controller Service + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @return the set of all relationships for which space is available to + * receive new objects + */ + Set<Relationship> getAvailableRelationships(); + + /** + * Indicates whether or not the given relationship is configured to be auto-terminated + * @param relationship the relationship + * @return <code>true</code> if the given relationship is auto-terminated, <code>false</code> otherwise + */ + boolean isAutoTerminated(Relationship relationship); + + /** + * @return true if the processor has one or more incoming connections, + * false otherwise + */ + boolean hasIncomingConnection(); + + /** + * @return <code>true</code> if the processor has one or more incoming connections for + * which the source of the connection is NOT the processor; returns <code>false</code> if + * the processor has no incoming connections or if all incoming connections are self-loops + * (i.e., the processor is also the source of all incoming connections). + */ + boolean hasNonLoopConnection(); + + /** + * @param relationship a relationship to check for connections + * @return true if the relationship has one or more outbound connections, + * false otherwise + */ + boolean hasConnection(Relationship relationship); + + /** + * @param property the Property whose value should be inspected to determined if it contains an Expression Language Expression + * @return <code>true</code> if the value of the given Property contains a NiFi Expression + * Language Expression, <code>false</code> if it does not. Note that <code>false</code> will be returned if the Property Descriptor + * does not allow the Expression Language, even if a seemingly valid Expression is present in the value. + */ + boolean isExpressionLanguagePresent(PropertyDescriptor property); + + /** + * @return the StateManager that can be used to store and retrieve state for this component + */ + StateManager getStateManager(); + + /** + * @return the configured name of this processor + */ + String getName(); + + /** + * @param relationship Relationship to be evaluated + * @return the configured relationships to be retried of this processor + */ + boolean isRelationshipRetried(Relationship relationship); + + /** + * @return the max number of times that the Processor will retry a FlowFile that is routed to a Relationship that is marked for Retry + */ + int getRetryCount(); +}
diff --git a/src/main/java/org/apache/nifi/processor/ProcessSession.java b/src/main/java/org/apache/nifi/processor/ProcessSession.java new file mode 100644 index 0000000..26106b1 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/ProcessSession.java
@@ -0,0 +1,901 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.components.state.StateMap; +import org.apache.nifi.controller.queue.QueueSize; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.exception.FlowFileAccessException; +import org.apache.nifi.processor.exception.FlowFileHandlingException; +import org.apache.nifi.processor.exception.MissingFlowFileException; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.io.InputStreamCallback; +import org.apache.nifi.processor.io.OutputStreamCallback; +import org.apache.nifi.processor.io.StreamCallback; +import org.apache.nifi.provenance.ProvenanceReporter; +import org.apache.nifi.provenance.ProvenanceEventType; + +/** + * A process session encompasses all the behaviors a processor can perform to + * obtain, clone, read, modify remove {@link FlowFile}s in an atomic unit. + * A process session is always tied to a single {@link Processor} at any one time + * and ensures no FlowFile can ever be accessed by any more than one processor at a given time. + * The session also ensures that all FlowFiles are always accounted for. + * The creator of a process session is always required to manage the session. + * <p> + * A session is not considered thread safe. The session supports a unit of work that is either committed or rolled back. + * <p> + * As noted on specific methods and for specific exceptions + * automated rollback will occur to ensure consistency of the repository. + * However, several situations can result in exceptions yet not cause automated rollback. + * In these cases the consistency of the repository will be retained + * but callers will be able to indicate whether it should result in rollback or continue on toward a commit. + * <p> + * A process session has two types of 'terminal' methods that will result in the session being in a 'fresh' state, + * containing no knowledge or any FlowFile, as if the session were newly created. + * After one of these methods is called, the instance may be used again. + * The terminal methods for a process session are {@link #commitAsync()} and {@link #rollback()} (and their overloads). + * <p> + * Additionally, the {@link #migrate(ProcessSession, Collection)} method transfers all knowledge of the provided FlowFiles + * to the other process session, as if the FlowFiles never existed in this process session. + * Note, however, that even if all FlowFiles are migrated via the {@link #migrate(ProcessSession, Collection)} method, + * the session is not necessarily entirely cleared, as it still may have knowledge of counter adjustments or state changes, + * e.g. see methods {@link #adjustCounter(String, long, boolean)} or {@link #setState(Map, Scope)}. + * A commit or rollback will clear these changes as well. + */ +public interface ProcessSession { + + /** + * Commits the current session ensuring all operations against {@link FlowFile}s within this session are atomically persisted. + * All FlowFiles operated on within this session must be accounted for by transfer or removal or the commit will fail. + * <p> + * As soon as the commit completes the session is again ready to be used. + * <p> + * See {@link #commitAsync()}, {@link #commitAsync(Runnable)}, and {@link #commitAsync(Runnable, Consumer)}. + * The asynchronous session commit methods are preferable to this commit call for most cases + * as it defers when the actual commit happens to the framework so that it can optimize when the commit call takes place. + * + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}). + * @throws FlowFileHandlingException if not all {@link FlowFile}s acted upon within this session are accounted for + * such that they have a transfer identified or where marked for removal. Automated rollback occurs. + * @throws ProcessException if some general fault occurs while persisting the session. + * Initiates automatic rollback. The root cause can be obtained via {@link Exception#getCause}. + */ + void commit(); + + /** + * Commits the current session ensuring all operations against {@link FlowFile}s within this session are atomically persisted. + * All FlowFiles operated on within this session must be accounted for by transfer or removal or the commit will fail. + * <p> + * Unlike the {@link #commit()} method, the persistence of data to the repositories + * is not guaranteed to have occurred by the time that this method returns. + * Therefore, if any follow-on actions are necessary after the data has been persisted to the repository + * (for example, acknowledging receipt from a source system, removing a source file, etc.) that logic + * should be performed only by invoking {@link #commitAsync(Runnable)} or {@link #commitAsync(Runnable, Consumer)} + * and implementing that action in the provided callback. + * <p> + * As a result, the following very common idiom: + * <pre><code> + * getDataFromSource(); + * session.commit(); + * acknowledgeReceiptOfData(); + * </code></pre> + * Cannot be simply changed to: + * <pre><code> + * getDataFromSource(); + * session.commitAsync(); + * acknowledgeReceiptOfData(); + * </code></pre> + * Doing so could result in acknowledging receipt of data from the source system before data has been committed to the repositories. + * If NiFi were to then be restarted, there is potential for data loss. + * Rather, the following idiom should take its place to ensure that there is no data loss: + * <pre><code> + * getDataFromSource(); + * session.commitAsync( () -> acknowledgeReceiptOfData() ); + * </code></pre> + * <p> + * If the session cannot be committed, an error will be logged and the session will be rolled back instead. + * + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}). + * @throws FlowFileHandlingException if not all {@link FlowFile}s acted upon within this session are accounted for + * such that they have a transfer identified or where marked for removal. Automated rollback occurs. + */ + void commitAsync(); + + /** + * Commits the current session ensuring all operations against {@link FlowFile}s within this session are atomically persisted. + * All FlowFiles operated on within this session must be accounted for by transfer or removal or the commit will fail. + * <p> + * If the session is successfully committed, the given {@code onSuccess} {@link Runnable} will be called. + * At the point that the session commit is completed, any calls to {@link #rollback()} / {@link #rollback(boolean)} + * will not undo that session commit but instead roll back any changes that may have occurred since. + * <p> + * If, for any reason, the session could not be committed, an error-level log message will be generated, + * but the caller will not have a chance to perform any cleanup logic. + * If such logic is necessary, use {@link #commitAsync(Runnable, Consumer)} instead. + * <p> + * Unlike the {@link #commit()} method, the persistence of data to the repositories + * is not guaranteed to have occurred by the time that this method returns. + * + * @param onSuccess {@link Runnable} that will be called if and when the session is successfully committed; may be null + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}). + * @throws FlowFileHandlingException if not all {@link FlowFile}s acted upon within this session are accounted for + * such that they have a transfer identified or where marked for removal. Automated rollback occurs. + */ + default void commitAsync(Runnable onSuccess) { + commitAsync(onSuccess, null); + } + + /** + * Commits the current session ensuring all operations against FlowFiles within this session are atomically persisted. + * All FlowFiles operated on within this session must be accounted for by transfer or removal or the commit will fail. + * <p> + * If the session is successfully committed, the given {@code onSuccess} {@link Runnable} will be called. + * At the point that the session commit is completed, any calls to {@link #rollback()} / {@link #rollback(boolean)} + * will not undo that session commit but instead roll back any changes that may have occurred since. + * <p> + * If, for any reason, the session could not be committed, the given {@code onFailure} {@link Consumer} will be called + * instead of the {@code onSuccess} {@link Runnable}. + * The Consumer will be provided the Throwable that prevented the session commit from completing. + * <p> + * Unlike the {@link #commit()} method, the persistence of data to the repositories + * is not guaranteed to have occurred by the time that this method returns. + * + * @param onSuccess {@link Runnable} that will be called if and when the session is successfully committed; may be null + * @param onFailure {@link Consumer} that will be called if, for any reason, the session could not be committed; may be null + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}). + * @throws FlowFileHandlingException if not all {@link FlowFile}s acted upon within this session are accounted for + * such that they have a transfer identified or where marked for removal. Automated rollback occurs. + */ + void commitAsync(Runnable onSuccess, Consumer<Throwable> onFailure); + + /** + * Reverts any changes made during this session. + * All {@link FlowFile}s are restored back to their initial session state and back to their original queues. + * If no changes were made since this session was last committed or rolled back, then this method has no effect. + * This method can be called any number of times. + * Calling this method is identical to calling {@link #rollback(boolean)} passing {@code false} as the parameter. + */ + void rollback(); + + /** + * Reverts any changes made during this session. + * All {@link FlowFile}s are restored back to their initial session state and back to their original queues, + * after optionally being penalized. + * If no changes were made since this session was last committed or rolled back, then this method has no effect. + * This method can be called any number of times. + * + * @param penalize whether the {@link FlowFile}s that are being restored back to their queues should be penalized + */ + void rollback(boolean penalize); + + /** + * Migrates ownership of the given {@code flowFiles} {@link FlowFile}s from {@code this} session to the given {@code newOwner} {@link ProcessSession}. + * <p> + * Note, that for any provided FlowFile, if the FlowFile has any child (e.g., by calling {@link #create(FlowFile)} + * and passing the FlowFile as the argument), then all children that were created must also be in the Collection of provided FlowFiles. + * <p> + * Also note, that if any FlowFile given is not the most up-to-date version of that FlowFile, + * then the most up-to-date version of the FlowFile will be migrated to the new owner. + * For example, if a call to {@link #putAttribute(FlowFile, String, String)} is made, + * passing {@code flowFile1} as the FlowFile, and then {@code flowFile1} is passed to this method, + * then the newest version (including the newly added attribute) will be migrated, + * not the outdated version of the FlowFile that {@code flowFile1} points to. + * + * @param newOwner the {@link ProcessSession} that is to become the new owner of the given {@link FlowFile}s + * @param flowFiles the {@link FlowFile}s to migrate + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the affected {@link FlowFile}s + */ + void migrate(ProcessSession newOwner, Collection<FlowFile> flowFiles); + + /** + * Migrates ownership of all {@link FlowFile}s from {@code this} session to the given {@code newOwner} {@link ProcessSession}. + * Calling this method is identical to calling {@link #migrate(ProcessSession, Collection)} + * passing all FlowFiles owned by this session as the parameter, + * this encompasses both FlowFiles retrieved from the work queue and newly created or cloned ones. + * + * @param newOwner the {@link ProcessSession} that is to become the new owner of all {@link FlowFile}s + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the affected {@link FlowFile}s + */ + void migrate(ProcessSession newOwner); + + /** + * Adjusts counter data for the given counter name and takes care of registering the counter if not already present. + * The adjustment occurs only if and when the process session is committed. + * + * @param name the name of the counter + * @param delta the delta by which to modify the counter (+ or -) + * @param immediate if true, the counter will be updated immediately, without regard to whether the session is committed or rolled back; + * otherwise, the counter will be incremented only if and when the session is committed. + */ + void adjustCounter(String name, long delta, boolean immediate); + + /** + * Returns the {@link FlowFile} from the work queue that is next highest priority to process. + * If no FlowFiles are available, returns {@code null}. + * + * @return the {@link FlowFile} from the work queue that is next highest priority to process or {@code null}, if none available + */ + FlowFile get(); + + /** + * Returns the next up to {@code maxResults} {@link FlowFile}s from the work queue that are the highest priority to process. + * If no FlowFiles are available, returns an empty list. Will not return {@code null}. + * <p> + * If multiple incoming queues are present, the behavior is unspecified in terms of + * whether all queues or only a single queue will be polled in a single call. + * + * @param maxResults the maximum number of {@link FlowFile}s to return + * @return up to {@code maxResults} {@link FlowFile}s from the work queue + * @throws IllegalArgumentException if {@code maxResults} is less than 0 + */ + List<FlowFile> get(int maxResults); + + /** + * Returns all {@link FlowFile}s from all the incoming queues which the given {@link FlowFileFilter} accepts. + * <p> + * Calls to this method provide exclusive access to the underlying queues. + * That is, no other thread will be permitted to pull FlowFiles from or add FlowFiles + * to this {@link Processor}'s incoming queues until this method call has returned. + * + * @param filter a {@link FlowFileFilter} to limit which {@link FlowFile}s are returned + * @return all {@link FlowFile}s from all the incoming queues which the given {@link FlowFileFilter} {@code filter} accepts. + */ + List<FlowFile> get(FlowFileFilter filter); + + /** + * Returns the {@link QueueSize} that represents the number of {@link FlowFile}s and their combined data size + * for all FlowFiles waiting to be processed by the {@link Processor} that owns {@code this} {@link ProcessSession}, + * regardless of which connection the FlowFiles live on. + * + * @return the number of {@link FlowFile}s and their combined data size in the work queue + */ + QueueSize getQueueSize(); + + /** + * Creates a new {@link FlowFile} in the repository with no content and without any linkage to a parent FlowFile. + * <p> + * This method is appropriate only when data is received or created from an external system. + * Otherwise, this method should be avoided and instead {@link #create(FlowFile)} or {@link #create(Collection)} be used. + * <p> + * When this method is used, a {@link ProvenanceEventType#CREATE} or {@link ProvenanceEventType#RECEIVE} event should be generated. + * See the {@link #getProvenanceReporter()} method and {@link ProvenanceReporter} class for more information. + * + * @return newly created FlowFile + */ + FlowFile create(); + + /** + * Creates a new {@link FlowFile} in the repository with no content but with a parent linkage to the {@code parent}. + * The newly created FlowFile will inherit all the parent's attributes, except for the UUID. + * <p> + * This method will automatically generate a {@link ProvenanceEventType#FORK} or a {@link ProvenanceEventType#JOIN} event, + * depending on whether other FlowFiles are generated from the same parent before the session is committed. + * + * @param parent to base the new {@link FlowFile} on, inheriting attributes from + * @return newly created {@link FlowFile} + */ + FlowFile create(FlowFile parent); + + /** + * Creates a new {@link FlowFile} in the repository with no content but with a parent linkage to all {@code parents}. + * The newly created FlowFile will inherit all the attributes that are in common to all parents, except for the UUID. + * <p> + * This method will automatically generate a {@link ProvenanceEventType#JOIN} event. + * + * @param parents to base the new {@link FlowFile} on, inheriting shared attributes from + * @return newly created {@link FlowFile} + */ + FlowFile create(Collection<FlowFile> parents); + + /** + * Creates a new {@link FlowFile} with a parent linkage to the {@code example} FlowFile. + * It is a clone of the given FlowFile as of the time this is called, both in attributes and content. + * <p> + * This method will automatically generate a {@link ProvenanceEventType#CLONE} event. + * + * @param example {@link FlowFile} to be the source of cloning - given FlowFile must be a part of the given session + * @return {@link FlowFile} that is a clone of the given {@code example} FlowFile + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code example} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + FlowFile clone(FlowFile example); + + /** + * Creates a new {@link FlowFile} with a parent linkage to the {@code parent} FlowFile. + * It is a clone of the given FlowFile as of the time this is called, both in attributes and a subset of the content. + * The content of the new FlowFile will be a subset of the byte sequence of the given FlowFile, + * starting at the specified offset and with the length specified. + * <p> + * This method will automatically generate a {@link ProvenanceEventType#FORK} or a {@link ProvenanceEventType#CLONE} event, + * if the offset is 0 and the size is exactly equal to the size of the example FlowFile. + * + * @param parent {@link FlowFile} to be the source of cloning - given FlowFile must be a part of the given session + * @param offset of the parent {@link FlowFile}'s content to base the cloned FlowFile's content on + * @param size in bytes of the parent {@link FlowFile}'s content to clone starting from the {@code offset} + * @return {@link FlowFile} that is a partial clone of the given {@code parent} FlowFile whose content has the specified {@code size} + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code parent} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Or if the specified {@code offset} + {@code size} exceeds the size of the {@code parent} FlowFile's content. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + FlowFile clone(FlowFile parent, long offset, long size); + + /** + * Sets a penalty for the given {@link FlowFile}, + * which will make it unavailable to be operated on any further during the penalty period. + * + * @param flowFile to penalize + * @return the penalized {@link FlowFile} + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile penalize(FlowFile flowFile); + + /** + * Updates the given {@link FlowFile}'s attributes with the given {@code key} / {@code value} pair. + * <p> + * If the {@code key} is named {@code uuid}, this attribute will be ignored. + * + * @param flowFile to update + * @param key of attribute to add or modify + * @param value of attribute to add or modify + * @return the updated {@link FlowFile} with the attribute added or modified + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile putAttribute(FlowFile flowFile, String key, String value); + + /** + * Updates the given {@link FlowFile}'s attributes with the given {@code key} / {@code value} pairs. + * <p> + * If the map contains a key named {@code uuid}, this attribute will be ignored. + * + * @param flowFile to update + * @param attributes the attributes to add or modify + * @return the updated {@link FlowFile} with the attributes added or modified + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile putAllAttributes(FlowFile flowFile, Map<String, String> attributes); + + /** + * Removes the attribute with the given {@code key} from the given {@link FlowFile}. + * <p> + * The attributes with the keys {@code uuid}, {@code path}, and {@code filename} will not be removed. + * If the {@code key} is one of those, this method will return the same FlowFile without removing any attribute. + * + * @param flowFile to update + * @param key of attribute to remove + * @return the updated {@link FlowFile} with the matching attribute removed + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile removeAttribute(FlowFile flowFile, String key); + + /** + * Removes the attributes with the given {@code keys} from the given {@link FlowFile}. + * <p> + * The attributes with the keys {@code uuid}, {@code path}, and {@code filename} will not be removed. + * + * @param flowFile to update + * @param keys of attributes to remove + * @return the updated {@link FlowFile} with the matching attributes removed + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile removeAllAttributes(FlowFile flowFile, Set<String> keys); + + /** + * Removes all attributes from the given {@link FlowFile} whose key matches the given pattern. + * <p> + * The attributes with the keys {@code uuid}, {@code path}, and {@code filename} will not be removed. + * + * @param flowFile to update + * @param keyPattern pattern to match each {@link FlowFile} attribute against; may be null, in which case no attribute is removed + * @return the updated {@link FlowFile} with the matching attributes removed + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + FlowFile removeAllAttributes(FlowFile flowFile, Pattern keyPattern); + + /** + * Transfers the given {@link FlowFile} back to the work queue from which it was pulled. + * <p> + * The processor will not be able to operate on the given FlowFile until this session is committed. + * Any modifications that have been made to the FlowFile will be maintained. + * FlowFiles that are created by the processor cannot be transferred back to themselves via this method. + * + * @param flowFile to transfer + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws IllegalArgumentException if the given {@link FlowFile} was created by this processor + */ + void transfer(FlowFile flowFile); + + /** + * Transfers the given {@link FlowFile}s back to the work queues from which the FlowFiles were pulled. + * <p> + * The processor will not be able to operate on the given FlowFiles until this session is committed. + * Any modifications that have been made to the FlowFiles will be maintained. + * FlowFiles that are created by the processor cannot be transferred back to themselves via this method. + * + * @param flowFiles to transfer + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if any of the given {@link FlowFile}s is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws IllegalArgumentException if any of the given {@link FlowFile}s was created by this processor + */ + void transfer(Collection<FlowFile> flowFiles); + + /** + * Transfers the given {@link FlowFile} to the appropriate destination processor work queue(s) based on the given {@code relationship}. + * <p> + * If the relationship leads to more than one destination the state of the FlowFile is replicated + * such that each destination receives an exact copy of the FlowFile though each will have its own unique identity. + * The destination processors will not be able to operate on the given FlowFile until this session is committed or + * until the ownership of the session is migrated to another processor. + * If ownership of the session is passed to a destination processor then that destination processor will have immediate visibility + * of the transferred FlowFiles within the session. + * + * @param flowFile to transfer + * @param relationship to transfer to + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws IllegalArgumentException if given relationship is not a known or registered relationship + */ + void transfer(FlowFile flowFile, Relationship relationship); + + /** + * Transfers the given {@link FlowFile}s to the appropriate destination processor work queue(s) based on the given {@code relationship}. + * <p> + * If the relationship leads to more than one destination the state of each FlowFile is replicated + * such that each destination receives an exact copy of the FlowFile though each will have its own unique identity. + * The destination processors will not be able to operate on the given FlowFiles until this session is committed or + * until the ownership of the session is migrated to another processor. + * If ownership of the session is passed to a destination processor then that destination processor will have immediate visibility + * of the transferred FlowFiles within the session. + * + * @param flowFiles to transfer + * @param relationship to transfer to + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the {@code flowFiles} {@link FlowFile}s + * @throws FlowFileHandlingException if any of the given {@link FlowFile}s is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws IllegalArgumentException if given relationship is not a known or registered relationship + */ + void transfer(Collection<FlowFile> flowFiles, Relationship relationship); + + /** + * Ends the managed persistence for the given {@link FlowFile}. + * <p> + * The persistent attributes for the FlowFile are deleted and so is the content assuming nothing else references it. + * This FlowFile will no longer be available for further operation. + * + * @param flowFile to remove + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + void remove(FlowFile flowFile); + + /** + * Ends the managed persistence for the given {@link FlowFile}s. + * <p> + * The persistent attributes for the FlowFiles are deleted and so is the content assuming nothing else references it. + * The FlowFiles will no longer be available for further operation. + * + * @param flowFiles to remove + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the given {@code flowFiles} {@link FlowFile}s + * @throws FlowFileHandlingException if any of the given {@link FlowFile}s is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + */ + void remove(Collection<FlowFile> flowFiles); + + /** + * Executes the given {code reader} {@link InputStreamCallback} against the content of the given {@link FlowFile}. + * + * @param source the {@link FlowFile} to retrieve the content from + * @param reader {@link InputStreamCallback} that will be called to read the {@link FlowFile} content + * @throws IllegalStateException if detected that this method is being called from within a write callback + * (see {@link #write(FlowFile, StreamCallback)}, {@link #write(FlowFile, OutputStreamCallback)}) + * or while a write stream is open (see {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile}. + * Said another way, it is not permissible to call this method while writing to the same FlowFile. + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to access the {@link InputStream} provided to the given {@link InputStreamCallback} + * after this method completed its execution + */ + void read(FlowFile source, InputStreamCallback reader) throws FlowFileAccessException; + + /** + * Provides an {@link InputStream} that can be used to read the content of the given {@link FlowFile}. + * <p> + * This method differs from those that make use of callbacks in that this method returns an InputStream and expects the caller + * to properly handle the lifecycle of the InputStream (i.e., the caller is responsible for ensuring that the InputStream is closed appropriately). + * The session may or may not handle closing the stream when the session is commited or rolled back, + * but the responsibility of doing so belongs to the caller. + * + * @param flowFile the {@link FlowFile} to retrieve the content from + * @return an {@link InputStream} that can be used to read the content of the {@link FlowFile} + * @throws IllegalStateException if detected that this method is being called from within a write callback + * (see {@link #write(FlowFile, StreamCallback)}, {@link #write(FlowFile, OutputStreamCallback)}) + * or while a write stream is open (see {@link #write(FlowFile)}) for the given {@code flowFile} {@link FlowFile}. + * Said another way, it is not permissible to call this method while writing to the same FlowFile. + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to read from the stream after the session is committed or rolled back. + */ + InputStream read(FlowFile flowFile); + + /** + * Combines the content of all given {@code sources} {@link FlowFile}s into a single given destination FlowFile. + * + * @param sources the {@link FlowFile}s whose content to merge + * @param destination the {@link FlowFile} to use as the merged result + * @return the updated {@code destination} destination {@link FlowFile} with changed content + * @throws IllegalArgumentException if the given destination is contained within the sources + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the given {@code sources} and {@code destination} {@link FlowFile}s + * @throws FlowFileHandlingException if any of the given {@link FlowFile}s is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if any of the given {@link FlowFile}'s content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * the state of the {@code destination} {@link FlowFile} will be as it was prior to this call. + */ + FlowFile merge(Collection<FlowFile> sources, FlowFile destination); + + /** + * Combines the content of all given {@code sources} {@link FlowFile}s into a single given destination FlowFile. + * + * @param sources the {@link FlowFile}s whose content to merge + * @param destination the {@link FlowFile} to use as the merged result + * @param header bytes that will be added to the beginning of the merged output; may be null or empty + * @param footer bytes that will be added to the end of the merged output; may be null or empty + * @param demarcator bytes that will be placed in between each object merged together; may be null or empty + * @return the updated {@code destination} {@link FlowFile} with changed content + * @throws IllegalArgumentException if the given destination is contained within the sources + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for any of the given {@code sources} and {@code destination} {@link FlowFile}s + * @throws FlowFileHandlingException if any of the given {@link FlowFile}s is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if any of the given {@link FlowFile}'s content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * the state of the {@code destination} {@link FlowFile} will be as it was prior to this call. + */ + FlowFile merge(Collection<FlowFile> sources, FlowFile destination, byte[] header, byte[] footer, byte[] demarcator); + + /** + * Executes the given {code writer} {@link OutputStreamCallback} against the content of the given {@link FlowFile}. + * + * @param source the {@link FlowFile} to write the content of + * @param writer {@link InputStreamCallback} that will be called to write the {@link FlowFile} content + * @return the updated {@code source} {@link FlowFile} with changed content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to access the {@link OutputStream} provided to the given {@link OutputStreamCallback} + * after this method completed its execution + */ + FlowFile write(FlowFile source, OutputStreamCallback writer) throws FlowFileAccessException; + + /** + * Provides an {@link OutputStream} that can be used to write the content of the given {@link FlowFile}. + * <p> + * This method differs from those that make use of callbacks in that this method returns an OutputStream and expects the caller + * to properly handle the lifecycle of the OutputStream (i.e., the caller is responsible for ensuring that the OutputStream is closed appropriately). + * The session may or may not handle closing the stream when the session is commited or rolled back, + * but the responsibility of doing so belongs to the caller. + * + * @param source the {@link FlowFile} to write the content of + * @return an {@link OutputStream} that can be used to write the content of the {@link FlowFile} + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to write to the stream after the session is committed or rolled back. + */ + OutputStream write(FlowFile source); + + /** + * Executes the given {code writer} {@link StreamCallback} against the content of the given {@link FlowFile}. + * + * @param source the {@link FlowFile} to read and write the content of + * @param writer {@link StreamCallback} that will be called to read and write the {@link FlowFile} content + * @return the updated {@code source} {@link FlowFile} with changed content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to access the {@link InputStream} or {@link OutputStream} + * provided to the given {@link StreamCallback} after this method completed its execution + */ + FlowFile write(FlowFile source, StreamCallback writer) throws FlowFileAccessException; + + /** + * Executes the given {code writer} {@link OutputStreamCallback} against the content of the given {@link FlowFile}, + * such that any data written to the OutputStream will be appended to the end of FlowFile's content. + * + * @param source the {@link FlowFile} to extend the content of + * @param writer {@link OutputStreamCallback} that will be called to append the {@link FlowFile}'s content + * @return the updated {@code source} {@link FlowFile} with changed content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content; + * if an attempt is made to access the {@link OutputStream} provided to the given {@link OutputStreamCallback} + * after this method completed its execution + */ + FlowFile append(FlowFile source, OutputStreamCallback writer) throws FlowFileAccessException; + + /** + * Writes to contents of the file a the {@code source} {@link Path} to the given {@link FlowFile}'s content. + * + * @param source the {@link Path} to the file from which content will be obtained + * @param keepSourceFile if true the content is simply copied; + * if false the original content might be used in a destructive way for efficiency, + * such that the repository will have the data but the original data will be gone. + * If false the source object will be removed or gone once imported. + * It will not be restored if the session is rolled back so this must be used with caution. + * In some cases it can result in tremendous efficiency gains but is also dangerous. + * @param destination the {@link FlowFile} whose content will be updated + * @return the updated {@code destination} {@link FlowFile} with changed content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + FlowFile importFrom(Path source, boolean keepSourceFile, FlowFile destination); + + /** + * Writes to contents of the {@code source} {@link InputStream} to the given {@link FlowFile}'s content. + * + * @param source the {@link InputStream} from which content will be obtained + * @param destination the {@link FlowFile} whose content will be updated + * @return the updated {@code destination} {@link FlowFile} with changed content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code source} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + FlowFile importFrom(InputStream source, FlowFile destination); + + /** + * Writes the content of the given {@link FlowFile} to the file at the given {@code destination} {@link Path}. + * + * @param flowFile the {@link FlowFile} to export the content of + * @param destination the {@link Path} to a file to export the {@link FlowFile}'s content to + * @param append if true will append to the current content of the file at the given path; + * if false will replace any current content + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + void exportTo(FlowFile flowFile, Path destination, boolean append); + + /** + * Writes the content of the given {@link FlowFile} to given {@code destination} {@link OutputStream}. + * + * @param flowFile the {@link FlowFile} to export the content of + * @param destination the {@link OutputStream} to export the {@link FlowFile}'s content to + * @throws IllegalStateException if detected that this method is being called from within a read or write callback + * (see {@link #read(FlowFile, InputStreamCallback)}, {@link #write(FlowFile, StreamCallback)}, + * {@link #write(FlowFile, OutputStreamCallback)}) or while a read or write stream is open + * (see {@link #read(FlowFile)}, {@link #write(FlowFile)}) for the given {@code flowFile} {@link FlowFile} + * @throws FlowFileHandlingException if the given {@link FlowFile} is already transferred or removed or doesn't belong to this session. + * Automatic rollback will occur. + * @throws MissingFlowFileException if the given {@link FlowFile} content cannot be found. + * The FlowFile should no longer be referenced, will be internally destroyed. The session is automatically rolled back. + * @throws FlowFileAccessException if some IO problem occurs accessing {@link FlowFile} content + */ + void exportTo(FlowFile flowFile, OutputStream destination); + + /** + * Returns the {@link ProvenanceReporter} that is tied to {@code this} {@link ProcessSession}. + * + * @return the {@link ProvenanceReporter} that is tied to {@code this} {@link ProcessSession} + */ + ProvenanceReporter getProvenanceReporter(); + + /** + * Updates the value of the component's state, setting it to given value. + * <p> + * This method does not update the remote State Provider immediately but rather caches the value until the session is committed. + * At that point, it will publish the state to the remote State Provider, if the state is the latest according to the remote State Provider. + * + * @param state the value to change the state to + * @param scope the {@link Scope} to use when storing the state + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + void setState(Map<String, String> state, Scope scope) throws IOException; + + /** + * Returns the current state for the component. + * <p> + * This return value will never be {@code null}. + * If the state has not yet been set, the StateMap's version will be -1, and the map of values will be empty. + * + * @param scope the {@link Scope} to use when fetching the state + * @return the current state for the component + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + StateMap getState(Scope scope) throws IOException; + + /** + * Updates the value of the component's state, setting it to given {@code newValue}, + * if and only if the current value is the same as the given {@code oldValue}. + * <p> + * The oldValue will be compared against the value of the state as it is known to {@code this} {@link ProcessSession}. + * If the Process Session does not currently know the state, it will be fetched from the StateProvider. + * <p> + * This method does not update the remote State Provider immediately but rather caches the value until the session is committed. + * At that point, it will publish the state to the remote State Provider, if the state is the latest according to the remote State Provider. + * + * @param oldValue the value to compare the state's current value against + * @param newValue the new value to use if and only if the state's current value is the same as the given {@code oldValue} + * @param scope the {@link Scope} to use for fetching the current and storing the new state + * @return {@code true} if the state was updated to the {@code newValue}, + * {@code false} if the state's current value was not equal to {@code oldValue} + * @throws IOException if unable to communicate with the underlying storage mechanism + */ + boolean replaceState(StateMap oldValue, Map<String, String> newValue, Scope scope) throws IOException; + + /** + * Clears all keys and values from the component's state. + * <p> + * This method does not update the remote State Provider immediately but rather caches the value until the session is committed. + * At that point, it will publish the state to the remote State Provider, if the state is the latest according to the remote State Provider. + * + * @param scope the {@link Scope} to use for clearing the state + * @throws IOException if unable to communicate with the underlying storage mechanism. + */ + void clearState(Scope scope) throws IOException; +}
diff --git a/src/main/java/org/apache/nifi/processor/ProcessSessionFactory.java b/src/main/java/org/apache/nifi/processor/ProcessSessionFactory.java new file mode 100644 index 0000000..06c0479 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/ProcessSessionFactory.java
@@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +/** + * <p> + * Factory for creating a {@link ProcessSession} + * </p> + * + * <p> + * MUST BE THREAD-SAFE</p> + */ +public interface ProcessSessionFactory { + + ProcessSession createSession(); +}
diff --git a/src/main/java/org/apache/nifi/processor/Processor.java b/src/main/java/org/apache/nifi/processor/Processor.java new file mode 100644 index 0000000..78851ec --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/Processor.java
@@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.migration.PropertyConfiguration; +import org.apache.nifi.migration.RelationshipConfiguration; +import org.apache.nifi.processor.exception.ProcessException; + +import java.util.Set; + +/** + * <p> + * Processor objects operate on FlowFile objects where the processors are linked + * together via relationships forming a directed graph structure.</p> + * + * <p> + * The validity of a processor once established is considered safe until a + * configuration property of that processor is changed. Even if the property + * change is itself valid it still brings the validity of the processor as a + * whole into question. Therefore changing a processor as a whole must be + * reverified. Also, changing any configuration property of a processor can also + * imply that its supported relationships have changed.</p> + * + * <p> + * Each Processor object is a single node in a flow graph. The same processor + * object on a graph may be called concurrently to update its configuration + * state and to perform 'onTrigger' actions. The framework also provides + * numerous hooks via annotations that subclasses can use to control the + * lifecycle of a given processor instance.</p> + * + * <p> + * Processor objects are expected to be thread-safe and must have a public + * default no-args constructor to facilitate the java service loader + * mechanism.</p> + * + */ +public interface Processor extends ConfigurableComponent { + + /** + * Provides the processor with access to objects that may be of use + * throughout the life of the Processor + * + * @param context of initialization + */ + void initialize(ProcessorInitializationContext context); + + /** + * @return Set of all relationships this processor expects to transfer a + * flow file to. An empty set indicates this processor does not have any + * destination relationships. Guaranteed non null. + */ + Set<Relationship> getRelationships(); + + /** + * <p> + * The method called when this processor is triggered to operate by the + * controller. In the absence of the {@link org.apache.nifi.annotation.behavior.TriggerSerially} annotation, + * this method may be called concurrently from different threads. + * When this method is called depends on how this processor is + * configured within a controller to be triggered (timing or event + * based).</p> + * + * @param context provides access to convenience methods for obtaining + * property values, delaying the scheduling of the processor, provides + * access to Controller Services, etc. + * @param sessionFactory provides access to a {@link ProcessSession}, which + * can be used for accessing FlowFiles, etc. + * + * @throws ProcessException if processing did not complete normally though + * indicates the problem is an understood potential outcome of processing. + * The controller/caller will handle these exceptions gracefully such as + * logging, etc.. If another type of exception is allowed to propagate the + * controller may no longer trigger this processor to operate, as this would + * indicate a probable coding defect. + */ + void onTrigger(ProcessContext context, ProcessSessionFactory sessionFactory) throws ProcessException; + + /** + * Indicates whether this processor, configured with the given {@link ProcessContext}, stores state. + * @param context provides access to convenience methods for obtaining + * property values, delaying the scheduling of the processor, provides + * access to Controller Services, etc. + * @return True if this processor stores state + */ + default boolean isStateful(ProcessContext context) { + return this.getClass().isAnnotationPresent(Stateful.class); + } + + /** + * <p> + * Allows for the migration of an old property configuration to a new configuration. This allows the Processor to evolve over time, + * as it allows properties to be renamed, removed, or reconfigured. + * </p> + * + * <p> + * This method is called only when a Processor is restored from a previous configuration. For example, when NiFi is restarted and the + * flow is restored from disk, when a previously configured flow is imported (e.g., from a JSON file that was exported or a NiFi Registry), + * or when a node joins a cluster and inherits a flow that has a new Processor. Once called, the method will not be invoked again for this + * Processor until NiFi is restarted. + * </p> + * + * @param config the current property configuration + */ + default void migrateProperties(PropertyConfiguration config) { + } + + /** + * Allows for the migration of an old relationship configuration to a new configuration + * + * @param config the current relationship configuration + */ + default void migrateRelationships(RelationshipConfiguration config) { + } +}
diff --git a/src/main/java/org/apache/nifi/processor/ProcessorInitializationContext.java b/src/main/java/org/apache/nifi/processor/ProcessorInitializationContext.java new file mode 100644 index 0000000..b4c7643 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/ProcessorInitializationContext.java
@@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.kerberos.KerberosContext; +import org.apache.nifi.logging.ComponentLog; + +/** + * <p> + * The <code>ProcessorInitializationContext</code> provides + * {@link org.apache.nifi.processor.Processor Processor}s access to objects that + * may be of use throughout the life of the Processor. + * </p> + */ +public interface ProcessorInitializationContext extends KerberosContext { + + /** + * @return the unique identifier for this processor + */ + String getIdentifier(); + + /** + * @return a {@link ComponentLog} that is tied to this processor that can be + * used to log events + */ + ComponentLog getLogger(); + + /** + * @return the {@link ControllerServiceLookup} which can be used to obtain + * Controller Services + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @return the {@link NodeTypeProvider} which can be used to detect the node + * type of this NiFi instance. + */ + NodeTypeProvider getNodeTypeProvider(); +}
diff --git a/src/main/java/org/apache/nifi/processor/Relationship.java b/src/main/java/org/apache/nifi/processor/Relationship.java new file mode 100644 index 0000000..c15c149 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/Relationship.java
@@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +/** + * An immutable object for holding information about a type of relationship. + * + */ +public final class Relationship implements Comparable<Relationship> { + + public static final Relationship SELF = new Relationship.Builder().build(); + public static final Relationship ANONYMOUS = new Relationship.Builder().name("").build(); + + /** + * The proper name of the relationship. Determines the relationship + * 'identity' + */ + private final String name; + /** + * A user displayable description of the purpose of this relationship. + */ + private final String description; + + /** + * The hash code, which is computed in the constructor because it is hashed + * very frequently and the hash code is constant + */ + private final int hashCode; + + /** + * The flag which tells the controller to auto terminate this + * relationship, so that the processor can be run even if it does + * not have connections from this relationship + */ + private final boolean isAutoTerminate; + + protected Relationship(final Builder builder) { + this.name = builder.name == null ? null : builder.name.intern(); + this.description = builder.description; + this.isAutoTerminate = builder.autoTerminate; + this.hashCode = 301 + ((name == null) ? 0 : this.name.hashCode()); // compute only once, since it gets called a bunch and will never change + } + + @Override + public int compareTo(final Relationship o) { + if (o == null) { + return -1; + } + final String thisName = getName(); + final String thatName = o.getName(); + if (thisName == null && thatName == null) { + return 0; + } + if (thisName == null) { + return 1; + } + if (thatName == null) { + return -1; + } + + return thisName.compareTo(thatName); + } + + public static final class Builder { + + private String name = ""; + private String description = ""; + private boolean autoTerminate = false; + + public Builder name(final String name) { + if (null != name) { + this.name = name; + } + return this; + } + + public Builder description(final String description) { + if (null != description) { + this.description = description; + } + return this; + } + + public Builder autoTerminateDefault(boolean autoTerminate) { + this.autoTerminate = autoTerminate; + return this; + } + + public Relationship build() { + return new Relationship(this); + } + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public boolean isAutoTerminated() { + return this.isAutoTerminate; + } + + @Override + public boolean equals(final Object other) { + if (other == null) { + return false; + } + if (!(other instanceof Relationship)) { + return false; + } + if (this == other) { + return true; + } + Relationship desc = (Relationship) other; + return this.name.equals(desc.name); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return this.name; + } +}
diff --git a/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java b/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java new file mode 100644 index 0000000..3421879 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java
@@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.processor; + +import org.apache.nifi.components.ConfigVerificationResult; +import org.apache.nifi.logging.ComponentLog; + +import java.util.List; +import java.util.Map; + +/** + * <p> + * Any Processor that implements this interface will be provided the opportunity to verify + * a given configuration of the Processor. This allows the Processor to provide meaningful feedback + * to users when configuring the dataflow. + * </p> + * + * <p> + * Generally speaking, verification differs from validation in that validation is expected to be very + * quick and run often. If a Processor is not valid, it cannot be started. However, verification may be + * more expensive or time-consuming to complete. For example, validation may ensure that a username is + * provided for connecting to an external service but should not perform any sort of network connection + * in order to verify that the username is accurate. Verification, on the other hand, may create resources + * such as network connections, may be more expensive to complete, and may be run only when a user invokes + * the action (though verification may later occur at other stages, such as when starting a component). + * </p> + * + * <p> + * Verification is allowed to be run only when a Processor is fully stopped. I.e., it has no active threads + * and currently has a state of STOPPED. Therefore, any initialization logic that may need to be performed + * before the Processor is triggered may also be required for verification. However, the framework is not responsible + * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such + * methods should be handled by the {@link #verify(ProcessContext, ComponentLog, Map)} itself. + * The {@link #verify(ProcessContext, ComponentLog, Map)} method will only be called if the configuration is valid according to the + * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid). + * </p> + */ +public interface VerifiableProcessor { + + /** + * Verifies that the configuration defined by the given ProcessContext is valid. + * @param context the ProcessContext that contains the necessary configuration + * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result + * in producing bulletins, which can be confusing. + * @param attributes a mapping of values that can be used as FlowFile attributes for the purpose of evaluating Expression Language for resolving property values + * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed + */ + List<ConfigVerificationResult> verify(ProcessContext context, ComponentLog verificationLogger, Map<String, String> attributes); + +}
diff --git a/src/main/java/org/apache/nifi/processor/exception/FlowFileAccessException.java b/src/main/java/org/apache/nifi/processor/exception/FlowFileAccessException.java new file mode 100644 index 0000000..c64ea1c --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/exception/FlowFileAccessException.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.exception; + +/** + * Indicates an issue occurred while accessing the content of a FlowFile, such + * as an IOException, or obtaining a reference to the FlowFile + * + */ +public class FlowFileAccessException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public FlowFileAccessException(final String message) { + super(message); + } + + public FlowFileAccessException(final String message, final Throwable cause) { + super(message, cause); + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/exception/FlowFileHandlingException.java b/src/main/java/org/apache/nifi/processor/exception/FlowFileHandlingException.java new file mode 100644 index 0000000..052f256 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/exception/FlowFileHandlingException.java
@@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.exception; + +/** + * Thrown when a flow file is referenced that is not part of the appropriate + * session or is not properly accounted for by a transfer or removal within a + * session. In any event this exception indicates a logic or programming error + * within the processor interacting with the offending session. + * + */ +public class FlowFileHandlingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public FlowFileHandlingException(final String message) { + super(message); + } + + public FlowFileHandlingException(final String message, final Throwable cause) { + super(message, cause); + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/exception/MissingFlowFileException.java b/src/main/java/org/apache/nifi/processor/exception/MissingFlowFileException.java new file mode 100644 index 0000000..700afd5 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/exception/MissingFlowFileException.java
@@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.exception; + +/** + * Thrown to indicate that the content for some FlowFile could not be found. + * This indicates that the data is gone and likely cannot be restored and any + * information about the FlowFile should be discarded entirely. This likely + * indicates some influence from an external process outside the control of the + * framework and it is not something any processor or the framework can recover + * so it must discard the object. + * + */ +public class MissingFlowFileException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public MissingFlowFileException(final String message, final Throwable cause) { + super(message, cause); + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/exception/ProcessException.java b/src/main/java/org/apache/nifi/processor/exception/ProcessException.java new file mode 100644 index 0000000..f162b14 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/exception/ProcessException.java
@@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.exception; + +/** + * Indicates an issue occurred in user code while processing a FlowFile. + * + */ +public class ProcessException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ProcessException() { + super(); + } + + public ProcessException(final String message) { + super(message); + } + + public ProcessException(final String message, final Throwable cause) { + super(message, cause); + } + + public ProcessException(final Throwable cause) { + super(cause); + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/exception/TerminatedTaskException.java b/src/main/java/org/apache/nifi/processor/exception/TerminatedTaskException.java new file mode 100644 index 0000000..a55ed62 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/exception/TerminatedTaskException.java
@@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.processor.exception; + +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.ProcessSessionFactory; + +/** + * This Exception is thrown whenever a user terminates a Processor and the + * Processor subsequently attempts to call a method on a {@link ProcessSession}, + * {@link ProcessSessionFactory}, {@link ProcessContext}, or an + * {@link InputStream} or {@link OutputStream} that were generated from the associated + * Process Sessions + */ +public class TerminatedTaskException extends ProcessException { + public TerminatedTaskException() { + super(); + } +}
diff --git a/src/main/java/org/apache/nifi/processor/io/InputStreamCallback.java b/src/main/java/org/apache/nifi/processor/io/InputStreamCallback.java new file mode 100644 index 0000000..9c04581 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/io/InputStreamCallback.java
@@ -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. + */ +package org.apache.nifi.processor.io; + +import java.io.IOException; +import java.io.InputStream; + +public interface InputStreamCallback { + + /** + * Provides a managed input stream for use. The input stream is + * automatically opened and closed though it is ok to close the stream + * manually. + * + * @param in the stream to read bytes from + * @throws IOException if issues reading from the underlying stream + */ + void process(InputStream in) throws IOException; + +}
diff --git a/src/main/java/org/apache/nifi/processor/io/OutputStreamCallback.java b/src/main/java/org/apache/nifi/processor/io/OutputStreamCallback.java new file mode 100644 index 0000000..35dca62 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/io/OutputStreamCallback.java
@@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.io; + +import java.io.IOException; +import java.io.OutputStream; + +public interface OutputStreamCallback { + + /** + * Provides a managed output stream for use. The input stream is + * automatically opened and closed though it is ok to close the stream + * manually - and quite important if any streams wrapping these streams open + * resources which should be cleared. + * + * @param out the stream to write bytes to + * @throws IOException if issues writing to output stream + */ + void process(OutputStream out) throws IOException; + +}
diff --git a/src/main/java/org/apache/nifi/processor/io/StreamCallback.java b/src/main/java/org/apache/nifi/processor/io/StreamCallback.java new file mode 100644 index 0000000..54f0e3b --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/io/StreamCallback.java
@@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public interface StreamCallback { + + /** + * Provides a managed output stream for use. The input stream is + * automatically opened and closed though it is ok to close the stream + * manually - and quite important if any streams wrapping these streams open + * resources which should be cleared. + * + * @param in the stream to read bytes from + * @param out the stream to write bytes to + * @throws IOException if issues occur reading or writing the underlying streams + */ + void process(InputStream in, OutputStream out) throws IOException; + +}
diff --git a/src/main/java/org/apache/nifi/processor/util/StandardValidators.java b/src/main/java/org/apache/nifi/processor/util/StandardValidators.java new file mode 100644 index 0000000..4ba2431 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
@@ -0,0 +1,969 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor.util; + +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; +import org.apache.nifi.expression.AttributeExpression.ResultType; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.DataUnit; +import org.apache.nifi.time.DurationFormat; + +import java.io.File; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.text.NumberFormat; +import java.text.ParseException; +import java.time.Instant; +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +public class StandardValidators { + + private StandardValidators() { + + } + + // + // + // STATICALLY DEFINED VALIDATORS + // + // + public static final Validator ATTRIBUTE_KEY_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final ValidationResult.Builder builder = new ValidationResult.Builder(); + builder.subject(subject).input(input); + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return builder.valid(true).explanation("Contains Expression Language").build(); + } + + try { + FlowFile.KeyValidator.validateKey(input); + builder.valid(true); + } catch (final IllegalArgumentException e) { + builder.valid(false).explanation(e.getMessage()); + } + + return builder.build(); + } + }; + + public static final Validator ATTRIBUTE_KEY_PROPERTY_NAME_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final ValidationResult.Builder builder = new ValidationResult.Builder(); + builder.subject("Property Name").input(subject); + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return builder.valid(true).explanation("Contains Expression Language").build(); + } + + try { + FlowFile.KeyValidator.validateKey(subject); + builder.valid(true); + } catch (final IllegalArgumentException e) { + builder.valid(false).explanation(e.getMessage()); + } + + return builder.build(); + } + }; + + public static final Validator POSITIVE_INTEGER_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + final int intVal = Integer.parseInt(value); + + if (intVal <= 0) { + reason = "not a positive value"; + } + } catch (final NumberFormatException e) { + reason = "not a valid integer"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator POSITIVE_LONG_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + final long longVal = Long.parseLong(value); + + if (longVal <= 0) { + reason = "not a positive value"; + } + } catch (final NumberFormatException e) { + reason = "not a valid 64-bit integer"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator NUMBER_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + NumberFormat.getInstance().parse(value); + } catch (ParseException e) { + reason = "not a valid Number"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator PORT_VALIDATOR = createLongValidator(0, 65535, true); + + /** + * {@link Validator} that ensures that value's length > 0 + */ + public static final Validator NON_EMPTY_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + return new ValidationResult.Builder().subject(subject).input(value).valid(value != null && !value.isEmpty()).explanation(subject + " cannot be empty").build(); + } + }; + + /** + * {@link Validator} that ensures that value's length > 0 and that expression language is present + */ + public static final Validator NON_EMPTY_EL_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(String subject, String input, ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + return NON_EMPTY_VALIDATOR.validate(subject, input, context); + } + }; + + /** + * {@link Validator} that ensures that value is a non-empty comma separated list of hostname:port + */ + public static final Validator HOSTNAME_PORT_LIST_VALIDATOR = new Validator() { + private static final Validator NON_ZERO_PORT_VALIDATOR = createLongValidator(1, 65535, true); + + @Override + public ValidationResult validate(String subject, String input, ValidationContext context) { + // expression language + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + // not empty + final ValidationResult nonEmptyValidatorResult = NON_EMPTY_VALIDATOR.validate(subject, input, context); + if (!nonEmptyValidatorResult.isValid()) { + return nonEmptyValidatorResult; + } + + // check format + final String[] hostnamePortList = input.split(","); + for (String hostnamePort : hostnamePortList) { + final String[] addresses = hostnamePort.split(":"); + // Protect against invalid input like http://127.0.0.1:9300 (URL scheme should not be there) + if (addresses.length != 2) { + return new ValidationResult.Builder().subject(subject).input(input).explanation( + "Must be in hostname:port form (no scheme such as http://").valid(false).build(); + } + + // Validate the port + final String port = addresses[1].trim(); + final ValidationResult portValidatorResult = NON_ZERO_PORT_VALIDATOR.validate(subject, port, context); + if (!portValidatorResult.isValid()) { + return portValidatorResult; + } + } + + return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid cluster definition").valid(true).build(); + } + }; + + /** + * {@link Validator} that ensures that value has 1+ non-whitespace + * characters + */ + public static final Validator NON_BLANK_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + return new ValidationResult.Builder().subject(subject).input(value) + .valid(value != null && !value.trim().isEmpty()) + .explanation(subject + + " must contain at least one character that is not white space").build(); + } + }; + + public static final Validator BOOLEAN_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + final boolean valid = "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); + final String explanation = valid ? null : "Value must be 'true' or 'false'"; + return new ValidationResult.Builder().subject(subject).input(value).valid(valid).explanation(explanation).build(); + } + }; + + public static final Validator INTEGER_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + Integer.parseInt(value); + } catch (final NumberFormatException e) { + reason = "not a valid integer"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator LONG_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + Long.parseLong(value); + } catch (final NumberFormatException e) { + reason = "not a valid Long"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator ISO8601_INSTANT_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + + try { + Instant.parse(input); + return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid ISO 8601 Instant Date").valid(true).build(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid ISO 8601 Instant Date, please enter in UTC time").valid(false).build(); + } + } + }; + + public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + final int intVal = Integer.parseInt(value); + + if (intVal < 0) { + reason = "value is negative"; + } + } catch (final NumberFormatException e) { + reason = "value is not a valid integer"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator CHARACTER_SET_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + final ResultType resultType = context.newExpressionLanguageCompiler().getResultType(value); + if (!resultType.equals(ResultType.STRING)) { + return new ValidationResult.Builder() + .subject(subject) + .input(value) + .valid(false) + .explanation("Expected Attribute Query to return type " + ResultType.STRING + " but query returns type " + resultType) + .build(); + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + if (!Charset.isSupported(value)) { + reason = "Character Set is not supported by this JVM."; + } + } catch (final UnsupportedCharsetException uce) { + reason = "Character Set is not supported by this JVM."; + } catch (final IllegalArgumentException iae) { + reason = "Character Set value cannot be null."; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + }; + + /** + * This validator will evaluate an expression using ONLY environment properties, + * then validate that the result is a supported character set. + */ + public static final Validator CHARACTER_SET_VALIDATOR_WITH_EVALUATION = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + String evaluatedInput = input; + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + try { + PropertyValue propertyValue = context.newPropertyValue(input); + evaluatedInput = (propertyValue == null) ? input : propertyValue.evaluateAttributeExpressions().getValue(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid expression").valid(false).build(); + } + } + + String reason = null; + try { + if (!Charset.isSupported(evaluatedInput)) { + reason = "Character Set is not supported by this JVM."; + } + } catch (final IllegalArgumentException iae) { + reason = "Character Set value is null or is not supported by this JVM."; + } + + return new ValidationResult.Builder().subject(subject).input(evaluatedInput).explanation(reason).valid(reason == null).build(); + } + }; + + public static final Validator SINGLE_CHAR_VALIDATOR = (subject, input, context) -> { + if (input == null) { + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(false) + .explanation("Input is null for this property") + .build(); + } + if (input.length() != 1) { + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(false) + .explanation("Value must be exactly 1 character but was " + input.length() + " in length") + .build(); + } + return new ValidationResult.Builder().input(input).subject(subject).valid(true).build(); + }; + + public static final Validator URL_VALIDATOR = new URLValidator(); + + public static final Validator URI_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + try { + new URI(input); + return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URI").valid(true).build(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid URI").valid(false).build(); + } + } + }; + + public static final Validator URI_LIST_VALIDATOR = (subject, input, context) -> { + + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + if (input == null || input.isEmpty()) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid URI, value is missing or empty").valid(false).build(); + } + + Optional<ValidationResult> invalidUri = Arrays.stream(input.split(",")) + .filter(uri -> uri != null && !uri.trim().isEmpty()) + .map(String::trim) + .map((uri) -> URI_VALIDATOR.validate(subject, uri, context)).filter((uri) -> !uri.isValid()).findFirst(); + + return invalidUri.orElseGet(() -> new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URI(s)").valid(true).build()); + }; + + public static final Validator REGULAR_EXPRESSION_VALIDATOR = createRegexValidator(0, Integer.MAX_VALUE, false); + + public static final Validator REGULAR_EXPRESSION_WITH_EL_VALIDATOR = createRegexValidator(0, Integer.MAX_VALUE, true); + + public static final Validator ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + try { + final String result = context.newExpressionLanguageCompiler().validateExpression(input, true); + if (!isEmpty(result)) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation(result).build(); + } + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation(e.getMessage()).build(); + } + } + + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } + + }; + + /** + * @param value to test + * @return true if value is null or empty string; does not trim before + * testing + */ + private static boolean isEmpty(final String value) { + return value == null || value.length() == 0; + } + + public static final Validator TIME_PERIOD_VALIDATOR = new Validator() { + private static final Pattern TIME_DURATION_PATTERN = Pattern.compile(DurationFormat.TIME_DURATION_REGEX); + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + if (input == null) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Time Period cannot be null").build(); + } + if (TIME_DURATION_PATTERN.matcher(input.toLowerCase()).matches()) { + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } else { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Must be of format <duration> <TimeUnit> where <duration> is a " + + "non-negative integer and TimeUnit is a supported Time Unit, such " + + "as: nanos, millis, secs, mins, hrs, days") + .build(); + } + } + }; + + public static final Validator DATA_SIZE_VALIDATOR = new Validator() { + private static final Pattern DATA_SIZE_PATTERN = Pattern.compile(DataUnit.DATA_SIZE_REGEX); + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + if (input == null) { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Data Size cannot be null") + .build(); + } + if (DATA_SIZE_PATTERN.matcher(input.toUpperCase()).matches()) { + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } else { + return new ValidationResult.Builder() + .subject(subject).input(input) + .valid(false) + .explanation("Must be of format <Data Size> <Data Unit> where <Data Size>" + + " is a non-negative integer and <Data Unit> is a supported Data" + + " Unit, such as: B, KB, MB, GB, TB") + .build(); + } + } + }; + + public static final Validator FILE_EXISTS_VALIDATOR = new FileExistsValidator(true); + + // + // + // FACTORY METHODS FOR VALIDATORS + // + // + public static Validator createDirectoryExistsValidator(final boolean allowExpressionLanguage, final boolean createDirectoryIfMissing) { + return new DirectoryExistsValidator(allowExpressionLanguage, createDirectoryIfMissing); + } + + public static Validator createListValidator(boolean trimEntries, boolean excludeEmptyEntries, + Validator elementValidator) { + return createListValidator(trimEntries, excludeEmptyEntries, elementValidator, false); + } + + public static Validator createListValidator(boolean trimEntries, boolean excludeEmptyEntries, + Validator validator, + boolean ensureElementValidation) { + return (subject, input, context) -> { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + try { + if (input == null) { + return new ValidationResult.Builder().subject(subject).input(null).explanation("List must have at least one non-empty element").valid(false).build(); + } + + final String[] list = ensureElementValidation ? input.split(",", -1) : input.split(","); + if (list.length == 0) { + return new ValidationResult.Builder().subject(subject).input(null).explanation("List must have at least one non-empty element").valid(false).build(); + } + + for (String item : list) { + String itemToValidate = trimEntries ? item.trim() : item; + if (!isEmpty(itemToValidate) || !excludeEmptyEntries) { + ValidationResult result = validator.validate(subject, itemToValidate, context); + if (!result.isValid()) { + return result; + } + } + } + return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid List").valid(true).build(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid list").valid(false).build(); + } + }; + } + + public static Validator createTimePeriodValidator(final long minTime, final TimeUnit minTimeUnit, final long maxTime, final TimeUnit maxTimeUnit) { + return new TimePeriodValidator(minTime, minTimeUnit, maxTime, maxTimeUnit); + } + + public static Validator createAttributeExpressionLanguageValidator(final ResultType expectedResultType) { + return createAttributeExpressionLanguageValidator(expectedResultType, true); + } + + public static Validator createDataSizeBoundsValidator(final long minBytesInclusive, final long maxBytesInclusive) { + return new Validator() { + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + final ValidationResult vr = DATA_SIZE_VALIDATOR.validate(subject, input, context); + if (!vr.isValid()) { + return vr; + } + final long dataSizeBytes = DataUnit.parseDataSize(input, DataUnit.B).longValue(); + if (dataSizeBytes < minBytesInclusive) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Cannot be smaller than " + minBytesInclusive + " bytes").build(); + } + if (dataSizeBytes > maxBytesInclusive) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Cannot be larger than " + maxBytesInclusive + " bytes").build(); + } + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } + }; + } + + + public static Validator createRegexMatchingValidator(final Pattern pattern) { + return createRegexMatchingValidator(pattern, false, "Value does not match regular expression: " + pattern.pattern()); + } + + public static Validator createRegexMatchingValidator(final Pattern pattern, final boolean evaluateExpressions, final String validationMessage) { + return new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + String value = input; + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + if (evaluateExpressions) { + try { + value = context.newPropertyValue(input).evaluateAttributeExpressions().getValue(); + } catch (final Exception e) { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Failed to evaluate the Attribute Expression Language due to " + e) + .build(); + } + } else { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .explanation("Expression Language Present") + .valid(true) + .build(); + } + } + + final boolean matches = value != null && pattern.matcher(value).matches(); + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(matches) + .explanation(matches ? null : validationMessage) + .build(); + } + }; + } + + public static Validator createRegexMatchingValidator(final Pattern pattern, final boolean evaluateExpressions) { + return createRegexMatchingValidator(pattern, evaluateExpressions, "Value does not match regular expression: " + pattern.pattern()); + } + + /** + * Creates a @{link Validator} that ensure that a value is a valid Java + * Regular Expression with at least <code>minCapturingGroups</code> + * capturing groups and at most <code>maxCapturingGroups</code> capturing + * groups. If <code>supportAttributeExpressionLanguage</code> is set to + * <code>true</code>, the value may also include the Expression Language, + * but the result of evaluating the Expression Language will be applied + * before the Regular Expression is performed. In this case, the Expression + * Language will not support FlowFile Attributes but only System/JVM + * Properties + * + * @param minCapturingGroups minimum capturing groups allowed + * @param maxCapturingGroups maximum capturing groups allowed + * @param supportAttributeExpressionLanguage whether or not to support + * expression language + * @return validator + */ + public static Validator createRegexValidator(final int minCapturingGroups, final int maxCapturingGroups, final boolean supportAttributeExpressionLanguage) { + return new Validator() { + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + try { + final String substituted; + if (supportAttributeExpressionLanguage) { + try { + substituted = context.newPropertyValue(value).evaluateAttributeExpressions().getValue(); + } catch (final Exception e) { + return new ValidationResult.Builder() + .subject(subject) + .input(value) + .valid(false) + .explanation("Failed to evaluate the Attribute Expression Language due to " + e) + .build(); + } + } else { + substituted = value; + } + + final Pattern pattern = Pattern.compile(substituted); + final int numGroups = pattern.matcher("").groupCount(); + if (numGroups < minCapturingGroups || numGroups > maxCapturingGroups) { + return new ValidationResult.Builder() + .subject(subject) + .input(value) + .valid(false) + .explanation("RegEx is required to have between " + minCapturingGroups + " and " + maxCapturingGroups + " Capturing Groups but has " + numGroups) + .build(); + } + + return new ValidationResult.Builder().subject(subject).input(value).valid(true).build(); + } catch (final Exception e) { + return new ValidationResult.Builder() + .subject(subject) + .input(value) + .valid(false) + .explanation("Not a valid Java Regular Expression") + .build(); + } + + } + }; + } + + public static Validator createAttributeExpressionLanguageValidator(final ResultType expectedResultType, final boolean allowExtraCharacters) { + return new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final String syntaxError = context.newExpressionLanguageCompiler().validateExpression(input, allowExtraCharacters); + if (syntaxError != null) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation(syntaxError).build(); + } + + final ResultType resultType = allowExtraCharacters ? ResultType.STRING : context.newExpressionLanguageCompiler().getResultType(input); + if (!resultType.equals(expectedResultType)) { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Expected Attribute Query to return type " + expectedResultType + " but query returns type " + resultType) + .build(); + } + + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } + }; + } + + public static Validator createLongValidator(final long minimum, final long maximum, final boolean inclusive) { + return new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + final long longVal = Long.parseLong(input); + if (longVal < minimum || (!inclusive && longVal == minimum) | longVal > maximum || (!inclusive && longVal == maximum)) { + reason = "Value must be between " + minimum + " and " + maximum + " (" + (inclusive ? "inclusive" : "exclusive") + ")"; + } + } catch (final NumberFormatException e) { + reason = "not a valid integer"; + } + + return new ValidationResult.Builder().subject(subject).input(input).explanation(reason).valid(reason == null).build(); + } + + }; + } + + public static Validator createNonNegativeFloatingPointValidator(final double maximum) { + return new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + String reason = null; + try { + final double doubleValue = Double.parseDouble(input); + if (doubleValue < 0) { + reason = "Value must be non-negative but was " + doubleValue; + } + final double maxPlusDelta = maximum + 0.00001D; + if (doubleValue < 0 || doubleValue > maxPlusDelta) { + reason = "Value must be between 0 and " + maximum + " but was " + doubleValue; + } + } catch (final NumberFormatException e) { + reason = "not a valid integer"; + } + + return new ValidationResult.Builder().subject(subject).input(input).explanation(reason).valid(reason == null).build(); + } + + }; + } + + // + // + // SPECIFIC VALIDATOR IMPLEMENTATIONS THAT CANNOT BE ANONYMOUS CLASSES + // + // + static class TimePeriodValidator implements Validator { + private static final Pattern pattern = Pattern.compile(DurationFormat.TIME_DURATION_REGEX); + + private final long minNanos; + private final long maxNanos; + + private final String minValueEnglish; + private final String maxValueEnglish; + + public TimePeriodValidator(final long minValue, final TimeUnit minTimeUnit, final long maxValue, final TimeUnit maxTimeUnit) { + this.minNanos = TimeUnit.NANOSECONDS.convert(minValue, minTimeUnit); + this.maxNanos = TimeUnit.NANOSECONDS.convert(maxValue, maxTimeUnit); + this.minValueEnglish = minValue + " " + minTimeUnit; + this.maxValueEnglish = maxValue + " " + maxTimeUnit; + } + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + if (input == null) { + return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Time Period cannot be null").build(); + } + final String lowerCase = input.toLowerCase(); + final boolean validSyntax = pattern.matcher(lowerCase).matches(); + final ValidationResult.Builder builder = new ValidationResult.Builder(); + if (validSyntax) { + final long nanos = DurationFormat.getTimeDuration(lowerCase, TimeUnit.NANOSECONDS); + + if (nanos < minNanos || nanos > maxNanos) { + builder.subject(subject).input(input).valid(false) + .explanation("Must be in the range of " + minValueEnglish + " to " + maxValueEnglish); + } else { + builder.subject(subject).input(input).valid(true); + } + } else { + builder.subject(subject).input(input).valid(false) + .explanation("Must be of format <duration> <TimeUnit> where <duration> is a non-negative " + + "integer and TimeUnit is a supported Time Unit, such as: nanos, millis, secs, mins, hrs, days"); + } + return builder.build(); + } + } + + public static class FileExistsValidator implements Validator { + + private final boolean allowEL; + private final boolean allowFileOnly; + + public FileExistsValidator(final boolean allowExpressionLanguage) { + this(allowExpressionLanguage, false); + } + + public FileExistsValidator(final boolean allowExpressionLanguage, final boolean fileOnly) { + this.allowEL = allowExpressionLanguage; + this.allowFileOnly = fileOnly; + } + + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + final String substituted; + if (allowEL) { + try { + substituted = context.newPropertyValue(value).evaluateAttributeExpressions().getValue(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(value).valid(false) + .explanation("Not a valid Expression Language value: " + e.getMessage()).build(); + } + } else { + substituted = value; + } + + final File file = new File(substituted); + if (!file.exists()) { + return new ValidationResult.Builder().subject(subject).input(value).valid(false).explanation("File " + file + " does not exist").build(); + } + if (allowFileOnly && !file.isFile()) { + return new ValidationResult.Builder().subject(subject).input(value).valid(false).explanation(file + " is not a file").build(); + } + return new ValidationResult.Builder().subject(subject).input(value).valid(true).build(); + } + } + + public static class StringLengthValidator implements Validator { + + private final int minimum; + private final int maximum; + + public StringLengthValidator(int minimum, int maximum) { + this.minimum = minimum; + this.maximum = maximum; + } + + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (value.length() < minimum || value.length() > maximum) { + return new ValidationResult.Builder() + .subject(subject) + .valid(false) + .input(value) + .explanation(String.format("String length invalid [min: %d, max: %d]", minimum, maximum)) + .build(); + } else { + return new ValidationResult.Builder() + .valid(true) + .input(value) + .subject(subject) + .build(); + } + } + } + + public static class DirectoryExistsValidator implements Validator { + + private final boolean allowEL; + private final boolean create; + + public DirectoryExistsValidator(final boolean allowExpressionLanguage, final boolean create) { + this.allowEL = allowExpressionLanguage; + this.create = create; + } + + @Override + public ValidationResult validate(final String subject, final String value, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) { + return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build(); + } + + final String substituted; + if (allowEL) { + try { + substituted = context.newPropertyValue(value).evaluateAttributeExpressions().getValue(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(value).valid(false) + .explanation("Not a valid Expression Language value: " + e.getMessage()).build(); + } + + if (substituted.trim().isEmpty() && !value.trim().isEmpty()) { + // User specified an Expression and nothing more... assume valid. + return new ValidationResult.Builder().subject(subject).input(value).valid(true).build(); + } + } else { + substituted = value; + } + + String reason = null; + try { + final File file = new File(substituted); + if (!file.exists()) { + if (!create) { + reason = "Directory does not exist"; + } else if (!file.mkdirs()) { + reason = "Directory does not exist and could not be created"; + } + } else if (!file.isDirectory()) { + reason = "Path does not point to a directory"; + } + } catch (final Exception e) { + reason = "Value is not a valid directory name"; + } + + return new ValidationResult.Builder().subject(subject).input(value).explanation(reason).valid(reason == null).build(); + } + } + +}
diff --git a/src/main/java/org/apache/nifi/processor/util/URLValidator.java b/src/main/java/org/apache/nifi/processor/util/URLValidator.java new file mode 100644 index 0000000..95d5322 --- /dev/null +++ b/src/main/java/org/apache/nifi/processor/util/URLValidator.java
@@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.processor.util; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class URLValidator implements Validator { + + private static final String SCHEME_PATTERN = "([^:/?#]+):"; + private static final String USERINFO_PATTERN = "([^@\\[/?#]*)"; + private static final String HOST_IPV4_PATTERN = "[^\\[/?#:]*"; + private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}:.]*[%\\p{Alnum}]*]"; + private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")"; + private static final String PORT_PATTERN = "(\\{[^}]+\\}?|[^/?#]*)"; + private static final String PATH_PATTERN = "([^?#]*)"; + private static final String QUERY_PATTERN = "([^#]*)"; + private static final String LAST_PATTERN = "(.*)"; + + // Regex patterns that matches URIs. See RFC 3986, appendix B + private static final Pattern URI_PATTERN = Pattern.compile( + "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + + ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?"); + + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build(); + } + + try { + // Check that we can parse the value as a URL + final String evaluatedInput = context.newPropertyValue(input).evaluateAttributeExpressions().getValue(); + createURL(evaluatedInput); + return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URL").valid(true).build(); + } catch (final Exception e) { + return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid URL").valid(false).build(); + } + } + + /** + * This method provides an alternative to the use of java.net.URI's single argument constructor and 'create' method. + * The drawbacks of the java.net.URI's single argument constructor and 'create' method are: + * <ul> + * <li>They do not provide quoting in the path section for any character not in the unreserved, punct, escaped, or other categories, + * and not equal to the slash character ('/') or the commercial-at character ('{@literal @}').</li> + * <li>They do not provide quoting for any illegal characters found in the query and fragment sections.</li> + * </ul> + * On the other hand, java.net.URI's seven argument constructor provides these quoting capabilities. In order + * to take advantage of this constructor, this method parses the given string into the arguments needed + * thereby allowing for instantiating a java.net.URI with the quoting of all illegal characters. + * @param url String representing a URL. + * @return Instance of java.net.URL + * @throws MalformedURLException if unable to create a URL from the given String representation + */ + public static URL createURL(final String url) throws MalformedURLException { + final Matcher matcher = URI_PATTERN.matcher(url); + if (matcher.matches()) { + final String scheme = matcher.group(2); + final String userInfo = matcher.group(5); + final String host = matcher.group(6); + final String port = matcher.group(8); + final String path = matcher.group(9); + final String query = matcher.group(11); + final String fragment = matcher.group(13); + + try { + return new URI(scheme, userInfo, host, port != null ? Integer.parseInt(port) : -1, path, query, fragment).toURL(); + } catch (final URISyntaxException e) { + throw new MalformedURLException("Unable to create URL from " + url + ": " + e.getMessage()); + } + } else { + throw new MalformedURLException(url + " is not a valid URL"); + } + } +}
diff --git a/src/main/java/org/apache/nifi/provenance/ProvenanceEventBuilder.java b/src/main/java/org/apache/nifi/provenance/ProvenanceEventBuilder.java new file mode 100644 index 0000000..0d212c2 --- /dev/null +++ b/src/main/java/org/apache/nifi/provenance/ProvenanceEventBuilder.java
@@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.provenance; + +import java.util.List; +import java.util.Map; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.Processor; +import org.apache.nifi.processor.Relationship; + +public interface ProvenanceEventBuilder { + + /** + * Sets the type of {@link ProvenanceEventRecord} + * + * @param eventType of the event + * @return the builder + */ + ProvenanceEventBuilder setEventType(ProvenanceEventType eventType); + + /** + * Populates the values of the Event being built from the values in the + * given event + * + * @param event the event from which to populate the Builders values + * @return the builder + */ + ProvenanceEventBuilder fromEvent(ProvenanceEventRecord event); + + /** + * Sets the date and time at which the FlowFile entered the flow + * + * @param entryDate of the flow file + * @return the builder + */ + ProvenanceEventBuilder setFlowFileEntryDate(long entryDate); + + /** + * Sets the Content Claim that the FlowFile was previously associated with + * before this event occurred. + * + * @param container for previous content + * @param section for previous content + * @param identifier for previous content + * @param offset for previous content + * @param size for previous content + * @return the builder + */ + ProvenanceEventBuilder setPreviousContentClaim(String container, String section, String identifier, Long offset, long size); + + /** + * Sets the Content Claim that the FlowFile is associated with as a result + * of this event + * + * @param container for resulting content + * @param section for resulting content + * @param identifier for resulting content + * @param offset for resulting content + * @param size for resulting content + * @return the builder + */ + ProvenanceEventBuilder setCurrentContentClaim(String container, String section, String identifier, Long offset, long size); + + /** + * Sets the identifier of the FlowFile Queue from which the FlowFile was + * pulled + * + * @param identifier of the source queue + * @return the builder + */ + ProvenanceEventBuilder setSourceQueueIdentifier(String identifier); + + /** + * Sets the attributes that existed on the FlowFile before this event + * occurred and any attributes that were added or updated as a result of + * this event. + * + * @param previousAttributes Map of all attributes before the event occurred + * @param updatedAttributes Map containing all attributes that were added or + * updated. If any entry has a value of <code>null</code>, that attribute is + * considered removed + * + * @return the builder + */ + ProvenanceEventBuilder setAttributes(Map<String, String> previousAttributes, Map<String, String> updatedAttributes); + + /** + * Sets the UUID to associate with the FlowFile + * + * @param uuid of the flowfile + * @return the builder + */ + ProvenanceEventBuilder setFlowFileUUID(String uuid); + + /** + * Sets the time at which the Provenance Event took place + * + * @param eventTime time of the event + * @return the builder + */ + ProvenanceEventBuilder setEventTime(long eventTime); + + /** + * Sets the amount of time that was required in order to perform the + * function referred to by this event + * + * @param millis of the event + * @return the builder + */ + ProvenanceEventBuilder setEventDuration(long millis); + + /** + * Sets the time at which the FlowFile's lineage began + * + * @param startDate start date of the event + * @return the builder + */ + ProvenanceEventBuilder setLineageStartDate(long startDate); + + /** + * Sets the unique identifier of the NiFi Component (such as a + * {@link Processor}) that is generating the Event + * + * @param componentId that produced the event + * @return the builder + */ + ProvenanceEventBuilder setComponentId(String componentId); + + /** + * Sets the type of the Component that is generating the Event. For + * {@link Processor}s, this is the Simple Class Name of the Processor. + * + * @param componentType of the component that made the event + * @return the builder + */ + ProvenanceEventBuilder setComponentType(String componentType); + + /** + * Sets the identifier that is used by the remote system to refer to the + * FlowFile for which this Event is being created. + * + * This is valid only for Event Types {@link ProvenanceEventType#RECEIVE} + * and {@link ProvenanceEventType#SEND} and will be ignored for any other + * event types. + * + * @param sourceSystemFlowFileIdentifier identifier the remote system used + * @return the builder + */ + ProvenanceEventBuilder setSourceSystemFlowFileIdentifier(String sourceSystemFlowFileIdentifier); + + /** + * Sets the Transit URI that is used for the Event. This is a URI that + * provides information about the System and Protocol information over which + * the transfer occurred. The intent of this field is such that both the + * sender and the receiver can publish the events to an external + * Enterprise-wide system that is then able to correlate the SEND and + * RECEIVE events. + * + * This is valid only for Event Types {@link ProvenanceEventType#RECEIVE} + * and {@link ProvenanceEventType#SEND} and will be ignored for any other + * event types. + * + * @param transitUri of the event + * @return the builder + */ + ProvenanceEventBuilder setTransitUri(String transitUri); + + /** + * Adds the given FlowFile as a parent for Events of type, + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} + * + * This is valid only for + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} events and will be ignored for any + * other event types. + * + * @param parent flowfile that this event is derived from + * @return the builder + */ + ProvenanceEventBuilder addParentFlowFile(FlowFile parent); + + /** + * Removes the given FlowFile as a parent for Events of type, + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} + * + * This is valid only for + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} events and will be ignored for any + * other event types. + * + * @param parent previous parent of this event + * @return the builder + */ + ProvenanceEventBuilder removeParentFlowFile(FlowFile parent); + + /** + * Adds the given FlowFile as a child for Events of type + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} + * + * This is valid only for + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} events and will be ignored for any + * other event types. + * + * @param child the child to add + * @return the builder + */ + ProvenanceEventBuilder addChildFlowFile(FlowFile child); + + /** + * Adds the given FlowFile identifier as a child for Events of type + * {@link ProvenanceEventType#FORK} and {@link ProvenanceEventType#CLONE} + * + * @param childId the ID of the FlowFile that is a child + * @return the builder + */ + ProvenanceEventBuilder addChildFlowFile(String childId); + + /** + * Removes the given FlowFile as a child for Events of type + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} + * + * This is valid only for + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, and + * {@link ProvenanceEventType#CLONE} events and will be ignored for any + * other event types. + * + * @param child to remove + * @return the builder + */ + ProvenanceEventBuilder removeChildFlowFile(FlowFile child); + + /** + * Associates the given identifier with the FlowFile for which this Event is + * created. This provides a linkage between the given FlowFile and alternate + * identifier. This information can be useful if published to an external, + * enterprise-wide Provenance tracking system that is able to associate the + * data between different processes or services. + * + * This is valid only for {@link ProvenanceEventType#ADDINFO} events and + * will be ignored for any other event types. + * + * @param alternateIdentifierUri another identifier of the flowfile this event is for + * @return the builder + */ + ProvenanceEventBuilder setAlternateIdentifierUri(String alternateIdentifierUri); + + /** + * Sets the details for this event. This is a free-form String that can + * contain any information that is relevant to this event. + * + * @param details a description of the event + * @return the builder + */ + ProvenanceEventBuilder setDetails(String details); + + /** + * Sets the to which the FlowFile was routed for + * {@link ProvenanceEventType#ROUTE} events. This is valid only for + * {@link ProvenanceEventType#ROUTE} events and will be ignored for any + * other event types. + * + * @param relationship to which flowfiles in this event were routed + * @return the builder + */ + ProvenanceEventBuilder setRelationship(Relationship relationship); + + /** + * Populates the builder with as much information as it can from the given + * FlowFile + * + * @param flowFile to source attributes for this event from + * @return the builder + */ + ProvenanceEventBuilder fromFlowFile(FlowFile flowFile); + + /** + * Builds the Provenance Event. It is possible that calling + * {@link ProvenanceEventRecord#getEventId()} on the + * {@link ProvenanceEventRecord} that is returned will yield + * <code>-1</code>. This is because the implementation of the Event may + * depend on the Provevenance Event Repository to generate the unique + * identifier. + * + * @return the event + */ + ProvenanceEventRecord build(); + + /** + * @return the ids of all FlowFiles that have been added as children via {@link #addChildFlowFile(FlowFile)} + */ + List<String> getChildFlowFileIds(); + + /** + * @return the ids of all FlowFiles that have been added as parents via {@link #addParentFlowFile(FlowFile)} + */ + List<String> getParentFlowFileIds(); + + /** + * @return the id of the FlowFile for which the event is being built + */ + String getFlowFileId(); + + /** + * @return a new Provenance Event Builder that is identical to this one (a deep copy) + */ + ProvenanceEventBuilder copy(); +}
diff --git a/src/main/java/org/apache/nifi/provenance/ProvenanceEventRecord.java b/src/main/java/org/apache/nifi/provenance/ProvenanceEventRecord.java new file mode 100644 index 0000000..b781afe --- /dev/null +++ b/src/main/java/org/apache/nifi/provenance/ProvenanceEventRecord.java
@@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.provenance; + +import java.util.List; +import java.util.Map; + +/** + * Describes an event that happened to a FlowFile. + */ +public interface ProvenanceEventRecord { + + String REMOTE_INPUT_PORT_TYPE = "Remote Input Port"; + String REMOTE_OUTPUT_PORT_TYPE = "Remote Output Port"; + + /** + * @return a unique ID for this Provenance Event. Depending on the + * implementation, the Event ID may be set to -1 until the event has been + * added to the {@link ProvenanceEventRepository} + */ + long getEventId(); + + /** + * @return the time at which this Provenance Event was created, as the + * number of milliseconds since epoch + */ + long getEventTime(); + + /** + * @return the EntryDate of the FlowFile to which this Event is associated + */ + long getFlowFileEntryDate(); + + /** + * @return the time at which the lineage began + */ + long getLineageStartDate(); + + /** + * @return the size of the FlowFile to which this Event is associated + */ + long getFileSize(); + + /** + * @return the previous size of the FlowFile to which this Event is + * associated, if the FlowFile previously had content and its size was + * known; otherwise, returns <code>null</code> + */ + Long getPreviousFileSize(); + + /** + * @return the amount of time in milliseconds that elapsed while performing + * this event. If not populated, the value -1 will be returned + */ + long getEventDuration(); + + /** + * @return the type of this Provenance Event + */ + ProvenanceEventType getEventType(); + + /** + * @return all FlowFile attributes that were associated with the FlowFile at + * the time that this ProvenanceEvent was created + */ + Map<String, String> getAttributes(); + + /** + * Returns the attribute with the given name + * + * @param attributeName the name of the attribute to get + * @return the attribute with the given name or <code>null</code> if no attribute exists with the given name + */ + default String getAttribute(String attributeName) { + return getAttributes().get(attributeName); + } + + /** + * @return all FlowFile attributes that existed on the FlowFile before this + * event occurred + */ + Map<String, String> getPreviousAttributes(); + + /** + * @return all FlowFile attributes that were updated as a result of this + * event + */ + Map<String, String> getUpdatedAttributes(); + + /** + * @return the ID of the Processor/component that created this Provenance + * Event + */ + String getComponentId(); + + /** + * @return the fully-qualified Class Name of the Processor/component that + * created this Provenance Event + */ + String getComponentType(); + + /** + * @return whether this event originated from a remote group port + */ + default boolean isRemotePortType() { + final String componentType = getComponentType(); + return REMOTE_INPUT_PORT_TYPE.equals(componentType) || REMOTE_OUTPUT_PORT_TYPE.equals(componentType); + } + + /** + * @return a URI that provides information about the System and Protocol + * information over which the transfer occurred. The intent of this field is + * such that both the sender and the receiver can publish the events to an + * external Enterprise-wide system that is then able to correlate the SEND + * and RECEIVE events. + */ + String getTransitUri(); + + /** + * Since the receiving system will usually refer to the data using a + * different identifier than the source system, this information is used to + * correlate the receive system's FlowFile with the sending system's data + * + * @return the UUID that the Source System used to refer to this data; this + * is applicable only when the {@link ProvenanceEventType} is of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} + */ + String getSourceSystemFlowFileIdentifier(); + + /** + * @return the UUID of the FlowFile with which this Event is associated + */ + String getFlowFileUuid(); + + /** + * @return the UUID's of all Parent FlowFiles. This is applicable only when + * the {@link ProvenanceEventType} is of type + * {@link ProvenanceEventType#CREATE CREATE} + */ + List<String> getParentUuids(); + + /** + * @return the UUID's of all Child FlowFiles. This is applicable only when + * the {@link ProvenanceEventType} is of type + * {@link ProvenanceEventType#CREATE CREATE} + */ + List<String> getChildUuids(); + + /** + * @return the Alternate Identifier associated with the FlowFile with which + * this Event is associated. This is applicable only when the + * {@link ProvenanceEventType} is of type + * {@link ProvenanceEventType#ADDINFO} + */ + String getAlternateIdentifierUri(); + + /** + * @return the details for this record, if any were supplied. Otherwise, + * returns <code>null</code> + */ + String getDetails(); + + /** + * @return the relationship to which this record was routed if the event + * type is {@link ProvenanceEventType#ROUTE}. The relationship is applicable + * only to this type + */ + String getRelationship(); + + /** + * @return the identifier of the queue from which the FlowFile was taken, if + * any. If the FlowFile is created as a result of this event (in this case, + * the Event Type is one of null null null null null null null null {@link ProvenanceEventType#CREATE}, {@link ProvenanceEventType#RECEIVE}, + * {@link ProvenanceEventType#FORK}, {@link ProvenanceEventType#JOIN}, or + * {@link ProvenanceEventType#CLONE}), or if the queue identifier is + * unknown, then this method will return <code>null</code> + */ + String getSourceQueueIdentifier(); + + /** + * @return the Section for the Content Claim that this Event refers to, if + * any; otherwise, returns <code>null</code> + */ + String getContentClaimSection(); + + /** + * @return the Section for the Content Claim that the FlowFile previously + * referenced, if any; otherwise, returns <code>null</code> + */ + String getPreviousContentClaimSection(); + + /** + * @return the Container for the Content Claim that this Event refers to, if + * any; otherwise, returns <code>null</code> + */ + String getContentClaimContainer(); + + /** + * @return the Container for the Content Claim that the FlowFile previously + * referenced, if any; otherwise, returns <code>null</code> + */ + String getPreviousContentClaimContainer(); + + /** + * @return the Identifier for the Content Claim that this Event refers to, + * if any; otherwise, returns <code>null</code> + */ + String getContentClaimIdentifier(); + + /** + * @return the Identifier for the Content Claim that the FlowFile previously + * referenced, if any; otherwise, returns <code>null</code> + */ + String getPreviousContentClaimIdentifier(); + + /** + * @return the offset into the Content Claim at which the FlowFile's content + * begins, if any; otherwise, returns <code>null</code> + */ + Long getContentClaimOffset(); + + /** + * @return the offset into the Content Claim at which the FlowFile's + * previous content began, if any; otherwise, returns <code>null</code> + */ + Long getPreviousContentClaimOffset(); + + /** + * Returns the best event identifier for this event (eventId if available, descriptive identifier if not yet persisted to allow for traceability). + * + * @return a descriptive event ID to allow tracing + */ + String getBestEventIdentifier(); +}
diff --git a/src/main/java/org/apache/nifi/provenance/ProvenanceEventRepository.java b/src/main/java/org/apache/nifi/provenance/ProvenanceEventRepository.java new file mode 100644 index 0000000..e9d3b14 --- /dev/null +++ b/src/main/java/org/apache/nifi/provenance/ProvenanceEventRepository.java
@@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.provenance; + +import java.io.IOException; +import java.util.List; + +/** + * This Repository houses Provenance Events. The repository is responsible for + * managing the life-cycle of the events, providing access to the events that it + * has stored, and providing query capabilities against the events. + * + */ +public interface ProvenanceEventRepository { + + /** + * Returns a {@link ProvenanceEventBuilder} that is capable of building + * {@link ProvenanceEventRecord}s + * + * @return builder + */ + ProvenanceEventBuilder eventBuilder(); + + /** + * Adds the given event to the repository and returns a new event for which + * the event id has been populated. Depending on the implementation, the + * returned event may or may not be the same event given + * + * @param event to register + */ + void registerEvent(ProvenanceEventRecord event); + + /** + * Adds the given events to the repository. + * + * <p> + * This interface makes no assumptions about whether or not the registration + * of the Collection are atomic. This detail is implementation-specific. + * </p> + * + * @param events to register + */ + void registerEvents(Iterable<ProvenanceEventRecord> events); + + /** + * Returns a List of all <code>ProvenanceEventRecord</code>s in the + * repository starting with the given ID. The first ID in the repository + * will always be 0 or higher. This method performs no authorization of + * the events. + * + * @param firstRecordId id of the first record to retrieve + * @param maxRecords maximum number of records to retrieve + * @return records + * @throws java.io.IOException if error reading from repository + */ + List<ProvenanceEventRecord> getEvents(long firstRecordId, final int maxRecords) throws IOException; + + + /** + * @return the largest ID of any event that is queryable in the repository. + * If no queryable events exists, returns null + */ + Long getMaxEventId(); + + /** + * Retrieves the Provenance Event with the given ID. + * + * @param id to lookup + * @return the Provenance Event Record with the given ID, if it exists, or + * {@code null} otherwise + * @throws IOException if failure while retrieving event + */ + ProvenanceEventRecord getEvent(long id) throws IOException; + + /** + * Closes the repository, freeing any resources + * + * @throws IOException if failure closing repository + */ + void close() throws IOException; + + +}
diff --git a/src/main/java/org/apache/nifi/provenance/ProvenanceEventType.java b/src/main/java/org/apache/nifi/provenance/ProvenanceEventType.java new file mode 100644 index 0000000..b5a8aff --- /dev/null +++ b/src/main/java/org/apache/nifi/provenance/ProvenanceEventType.java
@@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.provenance; + +public enum ProvenanceEventType { + + /** + * A CREATE event is used when a FlowFile is generated from data that was + * not received from a remote system or external process + */ + CREATE, + + /** + * Indicates a provenance event for receiving data from an external process. This Event Type + * is expected to be the first event for a FlowFile. As such, a Processor that receives data + * from an external source and uses that data to replace the content of an existing FlowFile + * should use the {@link #FETCH} event type, rather than the RECEIVE event type. + */ + RECEIVE, + + /** + * Indicates that the contents of a FlowFile were overwritten using the contents of some + * external resource. This is similar to the {@link #RECEIVE} event but varies in that + * RECEIVE events are intended to be used as the event that introduces the FlowFile into + * the system, whereas FETCH is used to indicate that the contents of an existing FlowFile + * were overwritten. + */ + FETCH, + + /** + * Indicates a provenance event for sending data to an external process + */ + SEND, + + /** + * Indicates a provenance event for sending data from a filesystem to an external process + */ + UPLOAD, + + /** + * Indicates a provenance event for sending remote invocation request to an external process. + * This event type is used to represent other operations than transferring data (RECEIVE, FETCH or SEND), + * for example, deleting object from an external process or storage. + */ + REMOTE_INVOCATION, + + /** + * Indicates that the contents of a FlowFile were downloaded by a user or external entity. + */ + DOWNLOAD, + + /** + * Indicates a provenance event for the conclusion of an object's life for + * some reason other than object expiration + */ + DROP, + + /** + * Indicates a provenance event for the conclusion of an object's life due + * to the fact that the object could not be processed in a timely manner + */ + EXPIRE, + + /** + * FORK is used to indicate that one or more FlowFile was derived from a + * parent FlowFile. + */ + FORK, + + /** + * JOIN is used to indicate that a single FlowFile is derived from joining + * together multiple parent FlowFiles. + */ + JOIN, + + /** + * CLONE is used to indicate that a FlowFile is an exact duplicate of its + * parent FlowFile. + */ + CLONE, + + /** + * CONTENT_MODIFIED is used to indicate that a FlowFile's content was + * modified in some way. When using this Event Type, it is advisable to + * provide details about how the content is modified. + */ + CONTENT_MODIFIED, + + /** + * ATTRIBUTES_MODIFIED is used to indicate that a FlowFile's attributes were + * modified in some way. This event is not needed when another event is + * reported at the same time, as the other event will already contain all + * FlowFile attributes. + */ + ATTRIBUTES_MODIFIED, + + /** + * ROUTE is used to show that a FlowFile was routed to a specified + * {@link org.apache.nifi.processor.Relationship Relationship} and should provide + * information about why the FlowFile was routed to this relationship. + */ + ROUTE, + + /** + * Indicates a provenance event for adding additional information such as a + * new linkage to a new URI or UUID + */ + ADDINFO, + + /** + * Indicates a provenance event for replaying a FlowFile. The UUID of the + * event will indicate the UUID of the original FlowFile that is being + * replayed. The event will contain exactly one Parent UUID that is also the + * UUID of the FlowFile that is being replayed and exactly one Child UUID + * that is the UUID of the a newly created FlowFile that will be re-queued + * for processing. + */ + REPLAY, + + /** + * Indicates that the type of the provenance event is unknown because the user + * who is attempting to access the event is not authorize to know the type. + */ + UNKNOWN; +}
diff --git a/src/main/java/org/apache/nifi/provenance/ProvenanceReporter.java b/src/main/java/org/apache/nifi/provenance/ProvenanceReporter.java new file mode 100644 index 0000000..a29a275 --- /dev/null +++ b/src/main/java/org/apache/nifi/provenance/ProvenanceReporter.java
@@ -0,0 +1,749 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.provenance; + +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.Relationship; + +import java.util.Collection; + +/** + * ProvenanceReporter generates and records Provenance-related events. A + * ProvenanceReporter is always tied to a {@link ProcessSession}. Any events + * that are generated are reported to Provenance only after the session has been + * committed. If the session is rolled back, the events related to that session + * are purged. + */ +public interface ProvenanceReporter { + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} that indicates that the given + * FlowFile was created from data received from an external source. + * + * @param flowFile the FlowFile that was received + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + */ + void receive(FlowFile flowFile, String transitUri); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} that indicates that the given + * FlowFile was created from data received from the specified URI and that + * the source system used the specified identifier (a URI with namespace) to + * refer to the data. + * + * @param flowFile the FlowFile that was received + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param sourceSystemFlowFileIdentifier the URI/identifier that the source + * system uses to refer to the data; if this value is non-null and is not a + * URI, the prefix "urn:tdo:" will be used to form a URI. + */ + void receive(FlowFile flowFile, String transitUri, String sourceSystemFlowFileIdentifier); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} that indicates that the given + * FlowFile was created from data received from an external source. + * + * @param flowFile the FlowFile that was received + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param transmissionMillis the number of milliseconds taken to transfer + * the data + */ + void receive(FlowFile flowFile, String transitUri, long transmissionMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} that indicates that the given + * FlowFile was created from data received from an external source and + * provides additional details about the receipt of the FlowFile, such as a + * remote system's Distinguished Name. + * + * @param flowFile the FlowFile that was received + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details details about the receive event; for example, it may be + * relevant to include the DN of the sending system + * @param transmissionMillis the number of milliseconds taken to transfer + * the data + */ + void receive(FlowFile flowFile, String transitUri, String details, long transmissionMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#RECEIVE RECEIVE} that indicates that the given + * FlowFile was created from data received from an external source and + * provides additional details about the receipt of the FlowFile, such as a + * remote system's Distinguished Name. + * + * @param flowFile the FlowFile that was received + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param sourceSystemFlowFileIdentifier the URI/identifier that the source + * system uses to refer to the data; if this value is non-null and is not a + * URI, the prefix "urn:tdo:" will be used to form a URI. + * @param details details about the receive event; for example, it may be + * relevant to include the DN of the sending system + * @param transmissionMillis the number of milliseconds taken to transfer + * the data + */ + void receive(FlowFile flowFile, String transitUri, String sourceSystemFlowFileIdentifier, String details, long transmissionMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#FETCH FETCH} that indicates that the content of the given + * FlowFile was overwritten with the data received from an external source. + * + * @param flowFile the FlowFile whose content was replaced + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. + */ + void fetch(FlowFile flowFile, String transitUri); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#FETCH FETCH} that indicates that the content of the given + * FlowFile was overwritten with the data received from an external source. + * + * @param flowFile the FlowFile whose content was replaced + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. + * @param transmissionMillis the number of milliseconds taken to transfer the data + */ + void fetch(FlowFile flowFile, String transitUri, long transmissionMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#FETCH FETCH} that indicates that the content of the given + * FlowFile was overwritten with the data received from an external source. + * + * @param flowFile the FlowFile whose content was replaced + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. + * @param details details about the event + * @param transmissionMillis the number of milliseconds taken to transfer + * the data + */ + void fetch(FlowFile flowFile, String transitUri, String details, long transmissionMillis); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + */ + void send(FlowFile flowFile, String transitUri); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details additional details related to the SEND event, such as a + * remote system's Distinguished Name + */ + void send(FlowFile flowFile, String transitUri, String details); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param transmissionMillis the number of milliseconds spent sending the + * data to the remote system + */ + void send(FlowFile flowFile, String transitUri, long transmissionMillis); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details additional details related to the SEND event, such as a + * remote system's Distinguished Name + * @param transmissionMillis the number of milliseconds spent sending the + * data to the remote system + */ + void send(FlowFile flowFile, String transitUri, String details, long transmissionMillis); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param force if <code>true</code>, this event will be added to the + * Provenance Repository immediately and will still be persisted if the + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} to which this + * ProvenanceReporter is associated is rolled back. Otherwise, the Event + * will be recorded only on a successful session commit. + */ + void send(FlowFile flowFile, String transitUri, boolean force); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details additional details related to the SEND event, such as a + * remote system's Distinguished Name + * @param force if <code>true</code>, this event will be added to the + * Provenance Repository immediately and will still be persisted if the + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} to which this + * ProvenanceReporter is associated is rolled back. Otherwise, the Event + * will be recorded only on a successful session commit. + */ + void send(FlowFile flowFile, String transitUri, String details, boolean force); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param transmissionMillis the number of milliseconds spent sending the + * data to the remote system + * @param force if <code>true</code>, this event will be added to the + * Provenance Repository immediately and will still be persisted if the + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} to which this + * ProvenanceReporter is associated is rolled back. Otherwise, the Event + * will be recorded only on a successful session commit. + */ + void send(FlowFile flowFile, String transitUri, long transmissionMillis, boolean force); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#SEND SEND} + * that indicates that a copy of the given FlowFile was sent to an external + * destination. The external destination may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details additional details related to the SEND event, such as a + * remote system's Distinguished Name + * @param transmissionMillis the number of milliseconds spent sending the + * data to the remote system + * @param force if <code>true</code>, this event will be added to the + * Provenance Repository immediately and will still be persisted if the + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} to which this + * ProvenanceReporter is associated is rolled back. Otherwise, the Event + * will be recorded only on a successful session commit. + */ + void send(FlowFile flowFile, String transitUri, String details, long transmissionMillis, boolean force); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#UPLOAD UPLOAD} + * that indicates that an external resource was sent to an external + * destination. The external resource may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param size the size of the resource in bytes that was uploaded + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + */ + void upload(FlowFile flowFile, long size, String transitUri); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#UPLOAD UPLOAD} + * that indicates that an external resource was sent to an external + * destination. The external resource may be a remote system or may be a + * local destination, such as the local file system but is external to NiFi. + * + * @param flowFile the FlowFile that was sent + * @param size the size of the resource in bytes that was uploaded + * @param transitUri A URI that provides information about the System and + * Protocol information over which the transfer occurred. The intent of this + * field is such that both the sender and the receiver can publish the + * events to an external Enterprise-wide system that is then able to + * correlate the SEND and RECEIVE events. + * @param details additional details related to the SEND event, such as a + * remote system's Distinguished Name + * @param transmissionMillis the number of milliseconds spent sending the + * data to the remote system + * @param force if <code>true</code>, this event will be added to the + * Provenance Repository immediately and will still be persisted if the + * {@link org.apache.nifi.processor.ProcessSession ProcessSession} to which this + * ProvenanceReporter is associated is rolled back. Otherwise, the Event + * will be recorded only on a successful session commit. + */ + void upload(FlowFile flowFile, long size, String transitUri, String details, long transmissionMillis, boolean force); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#REMOTE_INVOCATION} + * that indicates a remote invocation is requested to an external endpoint using + * the given FlowFile. The external endpoint may exist in a remote or a local system, + * but is external to NiFi. + * @param flowFile the FlowFile that was used to make the remote invocation + * @param transitUri A URI that provides information about the System and + * Protocol information over which the invocation occurred. The intent of this + * field is to identify they type and target resource or object of the invocation. + */ + void invokeRemoteProcess(FlowFile flowFile, String transitUri); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#REMOTE_INVOCATION} + * that indicates a remote invocation is requested to an external endpoint using + * the given FlowFile. The external endpoint may exist in a remote or a local system, + * but is external to NiFi. + * @param flowFile the FlowFile that was used to make the remote invocation + * @param transitUri A URI that provides information about the System and + * Protocol information over which the invocation occurred. The intent of this + * field is to identify they type and target resource or object of the invocation. + * @param details additional details related to the REMOTE_INVOCATION event, such as an + * explanation of the invoked process. + */ + void invokeRemoteProcess(FlowFile flowFile, String transitUri, String details); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#ADDINFO ADDINFO} that provides a linkage + * between the given FlowFile and alternate identifier. This information can + * be useful if published to an external, enterprise-wide Provenance + * tracking system that is able to associate the data between different + * processes. + * + * @param flowFile the FlowFile for which the association should be made + * @param alternateIdentifierNamespace the namespace of the alternate system + * @param alternateIdentifier the identifier that the alternate system uses + * when referring to the data that is encompassed by this FlowFile + */ + void associate(FlowFile flowFile, String alternateIdentifierNamespace, String alternateIdentifier); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#FORK FORK} + * that establishes that the given parent was split into multiple child + * FlowFiles. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parent the FlowFile from which the children are derived + * @param children the FlowFiles that are derived from the parent. + */ + void fork(FlowFile parent, Collection<FlowFile> children); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#FORK FORK} + * that establishes that the given parent was split into multiple child + * FlowFiles. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parent the FlowFile from which the children are derived + * @param children the FlowFiles that are derived from the parent. + * @param details any details pertinent to the fork + */ + void fork(FlowFile parent, Collection<FlowFile> children, String details); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#FORK FORK} + * that establishes that the given parent was split into multiple child + * FlowFiles. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parent the FlowFile from which the children are derived + * @param children the FlowFiles that are derived from the parent. + * @param forkDuration the number of milliseconds that it took to perform + * the task + */ + void fork(FlowFile parent, Collection<FlowFile> children, long forkDuration); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#FORK FORK} + * that establishes that the given parent was split into multiple child + * FlowFiles. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parent the FlowFile from which the children are derived + * @param children the FlowFiles that are derived from the parent. + * @param details any details pertinent to the fork + * @param forkDuration the number of milliseconds that it took to perform + * the task + */ + void fork(FlowFile parent, Collection<FlowFile> children, String details, long forkDuration); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#JOIN JOIN} + * that establishes that the given parents were joined together to create a + * new child FlowFile. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parents the FlowFiles that are being joined together to create the + * child + * @param child the FlowFile that is being created by joining the parents + */ + void join(Collection<FlowFile> parents, FlowFile child); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#JOIN JOIN} + * that establishes that the given parents were joined together to create a + * new child FlowFile. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parents the FlowFiles that are being joined together to create the + * child + * @param child the FlowFile that is being created by joining the parents + * @param details any details pertinent to the event + */ + void join(Collection<FlowFile> parents, FlowFile child, String details); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#JOIN JOIN} + * that establishes that the given parents were joined together to create a + * new child FlowFile. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parents the FlowFiles that are being joined together to create the + * child + * @param child the FlowFile that is being created by joining the parents + * @param joinDuration the number of milliseconds that it took to join the + * FlowFiles + */ + void join(Collection<FlowFile> parents, FlowFile child, long joinDuration); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#JOIN JOIN} + * that establishes that the given parents were joined together to create a + * new child FlowFile. In general, this method does not need to be called by + * Processors, as the ProcessSession will handle this automatically for you + * when calling {@link ProcessSession#create(FlowFile)}. + * + * @param parents the FlowFiles that are being joined together to create the + * child + * @param child the FlowFile that is being created by joining the parents + * @param details any details pertinent to the event + * @param joinDuration the number of milliseconds that it took to join the + * FlowFiles + */ + void join(Collection<FlowFile> parents, FlowFile child, String details, long joinDuration); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#CLONE CLONE} + * that establishes that the given child is an exact replica of the parent. + * In general, this method does not need to be called by Processors, as the + * {@link ProcessSession} will handle this automatically for you when + * calling {@link ProcessSession#clone(FlowFile)} + * + * @param parent the FlowFile that was cloned + * @param child the clone + */ + void clone(FlowFile parent, FlowFile child); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CONTENT_MODIFIED CONTENT_MODIFIED} that + * indicates that the content of the given FlowFile has been modified. One + * of the <code>modifyContent</code> methods should be called any time that + * the contents of a FlowFile are modified. + * + * @param flowFile the FlowFile whose content is being modified + */ + void modifyContent(FlowFile flowFile); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CONTENT_MODIFIED CONTENT_MODIFIED} that + * indicates that the content of the given FlowFile has been modified. One + * of the <code>modifyContent</code> methods should be called any time that + * the contents of a FlowFile are modified. + * + * @param flowFile the FlowFile whose content is being modified + * @param details Any details about how the content of the FlowFile has been + * modified. Details should not be specified if they can be inferred by + * other information in the event, such as the name of the Processor, as + * specifying this information will add undue overhead + */ + void modifyContent(FlowFile flowFile, String details); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CONTENT_MODIFIED CONTENT_MODIFIED} that + * indicates that the content of the given FlowFile has been modified. One + * of the <code>modifyContent</code> methods should be called any time that + * the contents of a FlowFile are modified. + * + * @param flowFile the FlowFile whose content is being modified + * @param processingMillis the number of milliseconds spent processing the + * FlowFile + */ + void modifyContent(FlowFile flowFile, long processingMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CONTENT_MODIFIED CONTENT_MODIFIED} that + * indicates that the content of the given FlowFile has been modified. One + * of the <code>modifyContent</code> methods should be called any time that + * the contents of a FlowFile are modified. + * + * @param flowFile the FlowFile whose content is being modified + * @param details Any details about how the content of the FlowFile has been + * modified. Details should not be specified if they can be inferred by + * other information in the event, such as the name of the Processor, as + * specifying this information will add undue overhead + * @param processingMillis the number of milliseconds spent processing the + * FlowFile + */ + void modifyContent(FlowFile flowFile, String details, long processingMillis); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#ATTRIBUTES_MODIFIED ATTRIBUTES_MODIFIED} that + * indicates that the Attributes of the given FlowFile were updated. It is + * not necessary to emit such an event for a FlowFile if other Events are + * already emitted by a Processor. For example, one should call both + * {@link #modifyContent(FlowFile)} and {@link #modifyAttributes(FlowFile)} + * for the same FlowFile in the same Processor. Rather, the Processor should + * call just the {@link #modifyContent(FlowFile)}, as the call to + * {@link #modifyContent(FlowFile)} will generate a Provenance Event that + * already contains all FlowFile attributes. As such, emitting another event + * that contains those attributes is unneeded and can result in a + * significant amount of overhead for storage and processing. + * + * @param flowFile the FlowFile whose attributes were modified + */ + void modifyAttributes(FlowFile flowFile); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#ATTRIBUTES_MODIFIED ATTRIBUTES_MODIFIED} that + * indicates that the Attributes of the given FlowFile were updated. It is + * not necessary to emit such an event for a FlowFile if other Events are + * already emitted by a Processor. For example, one should call both + * {@link #modifyContent(FlowFile)} and {@link #modifyAttributes(FlowFile)} + * for the same FlowFile in the same Processor. Rather, the Processor should + * call just the {@link #modifyContent(FlowFile)}, as the call to + * {@link #modifyContent(FlowFile)} will generate a Provenance Event that + * already contains all FlowFile attributes. As such, emitting another event + * that contains those attributes is unneeded and can result in a + * significant amount of overhead for storage and processing. + * + * @param flowFile the FlowFile whose attributes were modified + * @param details any details should be provided about the attribute + * modification + */ + void modifyAttributes(FlowFile flowFile, String details); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#ROUTE ROUTE} + * that indicates that the given FlowFile was routed to the given + * {@link Relationship}. <b>Note: </b> this Event is intended for Processors + * whose sole job it is to route FlowFiles and should NOT be used as a way + * to indicate that the given FlowFile was routed to a standard 'success' or + * 'failure' relationship. Doing so can be problematic, as DataFlow Managers + * often will loop 'failure' relationships back to the same processor. As + * such, emitting a Route event to indicate that a FlowFile was routed to + * 'failure' can result in creating thousands of Provenance Events for a + * given FlowFile, resulting in a very difficult-to- understand lineage. + * + * @param flowFile the FlowFile being routed + * @param relationship the Relationship to which the FlowFile was routed + */ + void route(FlowFile flowFile, Relationship relationship); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#ROUTE ROUTE} + * that indicates that the given FlowFile was routed to the given + * {@link Relationship}. <b>Note: </b> this Event is intended ONLY for + * Processors whose sole job it is to route FlowFiles and should NOT be used + * as a way to indicate that hte given FlowFile was routed to a standard + * 'success' or 'failure' relationship. Doing so can be problematic, as + * DataFlow Managers often will loop 'failure' relationships back to the + * same processor. As such, emitting a Route event to indicate that a + * FlowFile was routed to 'failure' can result in creating thousands of + * Provenance Events for a given FlowFile, resulting in a very difficult-to- + * understand lineage. + * + * @param flowFile the FlowFile being routed + * @param relationship the Relationship to which the FlowFile was routed + * @param details any details pertinent to the Route event, such as why the + * FlowFile was routed to the specified Relationship + */ + void route(FlowFile flowFile, Relationship relationship, String details); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#ROUTE ROUTE} + * that indicates that the given FlowFile was routed to the given + * {@link Relationship}. <b>Note: </b> this Event is intended ONLY for + * Processors whose sole job it is to route FlowFiles and should NOT be used + * as a way to indicate that hte given FlowFile was routed to a standard + * 'success' or 'failure' relationship. Doing so can be problematic, as + * DataFlow Managers often will loop 'failure' relationships back to the + * same processor. As such, emitting a Route event to indicate that a + * FlowFile was routed to 'failure' can result in creating thousands of + * Provenance Events for a given FlowFile, resulting in a very difficult-to- + * understand lineage. + * + * @param flowFile the FlowFile being routed + * @param relationship the Relationship to which the FlowFile was routed + * @param processingDuration the number of milliseconds that it took to + * determine how to route the FlowFile + */ + void route(FlowFile flowFile, Relationship relationship, long processingDuration); + + /** + * Emits a Provenance Event of type {@link ProvenanceEventType#ROUTE ROUTE} + * that indicates that the given FlowFile was routed to the given + * {@link Relationship}. <b>Note: </b> this Event is intended ONLY for + * Processors whose sole job it is to route FlowFiles and should NOT be used + * as a way to indicate that hte given FlowFile was routed to a standard + * 'success' or 'failure' relationship. Doing so can be problematic, as + * DataFlow Managers often will loop 'failure' relationships back to the + * same processor. As such, emitting a Route event to indicate that a + * FlowFile was routed to 'failure' can result in creating thousands of + * Provenance Events for a given FlowFile, resulting in a very difficult-to- + * understand lineage. + * + * @param flowFile the FlowFile being routed + * @param relationship the Relationship to which the FlowFile was routed + * @param details any details pertinent to the Route event, such as why the + * FlowFile was routed to the specified Relationship + * @param processingDuration the number of milliseconds that it took to + * determine how to route the FlowFile + */ + void route(FlowFile flowFile, Relationship relationship, String details, long processingDuration); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CREATE CREATE} that indicates that the given + * FlowFile was created by NiFi from data that was not received from an + * external entity. If the data was received from an external source, use + * the {@link #receive(FlowFile, String)} event instead + * + * @param flowFile the FlowFile that was created + */ + void create(FlowFile flowFile); + + /** + * Emits a Provenance Event of type + * {@link ProvenanceEventType#CREATE CREATE} that indicates that the given + * FlowFile was created by NiFi from data that was not received from an + * external entity. If the data was received from an external source, use + * the {@link #receive(FlowFile, String, String, long)} event instead + * + * @param flowFile the FlowFile that was created + * @param details any relevant details about the CREATE event + */ + void create(FlowFile flowFile, String details); + /** + * @return the number of FlowFiles for which there was a RECEIVE event + */ + int getFlowFilesReceived(); + + /** + * @return the sum of the sizes of all FlowFiles for which there was a RECEIVE event + */ + long getBytesReceived(); + + /** + * @return the number of FlowFiles for which there was a FETCH event + */ + int getFlowFilesFetched(); + + /** + * @return the sum of the sizes of all FlowFiles for which there was a FETCH event + */ + long getBytesFetched(); + + /** + * @return the number of FlowFiles for which there was a SEND event + */ + int getFlowFilesSent(); + + /** + * @return the sum of the sizes of all FlowFiles for which there was a SEND event + */ + long getBytesSent(); +}
diff --git a/src/main/java/org/apache/nifi/registry/EnvironmentVariables.java b/src/main/java/org/apache/nifi/registry/EnvironmentVariables.java new file mode 100644 index 0000000..52721a8 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/EnvironmentVariables.java
@@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Provides a registry of variables available for use by various components and + * extension points. This enables components to reference variable names rather + * than explicit values which can make configurations of those components more + * portable. + */ +public interface EnvironmentVariables { + + /** + * Provides a registry containing all environment variables and system + * properties. System properties receive precedence. + */ + EnvironmentVariables ENVIRONMENT_VARIABLES = new EnvironmentVariables() { + final Map<VariableDescriptor, String> map = new HashMap<>(); + + { + System.getenv().entrySet().stream().forEach((entry) -> { + final VariableDescriptor desc = new VariableDescriptor.Builder(entry.getKey()) + .description("Env Var") + .sensitive(false) + .build(); + map.put(desc, entry.getValue()); + }); + System.getProperties().entrySet().stream().forEach((entry) -> { + final VariableDescriptor desc = new VariableDescriptor.Builder(entry.getKey().toString()) + .description("System Property") + .sensitive(false) + .build(); + map.put(desc, entry.getValue().toString()); + }); + } + + @Override + public Map<VariableDescriptor, String> getEnvironmentVariablesMap() { + return Collections.unmodifiableMap(map); + } + + }; + + /** + * Provides access to a map of variable key/value pairs. For variables + * considered to be sensitive care must be taken to ensure their values are + * protected whenever stored or exposed. + * + * @return An immutable map of all variables in the registry + */ + + Map<VariableDescriptor, String> getEnvironmentVariablesMap(); + + /** + * Returns the VariableDescriptor for the given key name if it exists. + * + * @param name the string name of the VariableDescriptor to lookup. + * @return the variable descriptor registered for this name if it exists; + * null otherwise + */ + default VariableDescriptor getEnvironmentVariableKey(final String name) { + if (name == null) { + return null; + } + final VariableDescriptor spec = new VariableDescriptor(name); + for (final Map.Entry<VariableDescriptor, String> entry : getEnvironmentVariablesMap().entrySet()) { + if (entry.getKey().equals(spec)) { + return entry.getKey(); + } + } + return null; + + } + + /** + * Gets the variable value + * + * @param name the string name of the VariableDescriptor that is the key of + * the value to lookup. + * @return the value associated with the given variable name if found; null + * otherwise + */ + default String getEnvironmentVariableValue(final String name) { + if (name == null) { + return null; + } + return getEnvironmentVariablesMap().get(new VariableDescriptor(name)); + } + + /** + * Gets the variable value + * + * @param descriptor the descriptor for which to lookup the variable value. + * @return the variable value if the given descriptor is equivalent to one + * of the entries in the registry; null otherwise + */ + default String getEnvironmentVariableValue(final VariableDescriptor descriptor) { + if (descriptor == null) { + return null; + } + return getEnvironmentVariablesMap().get(descriptor); + } + +}
diff --git a/src/main/java/org/apache/nifi/registry/VariableDescriptor.java b/src/main/java/org/apache/nifi/registry/VariableDescriptor.java new file mode 100644 index 0000000..f9e9ce2 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/VariableDescriptor.java
@@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry; + +/** + * An immutable object for holding information about a type of processor + * property. + * + */ +public final class VariableDescriptor implements Comparable<VariableDescriptor> { + + /** + * The name (or key) of the variable by which all access/lookups to the + * value will occur. This is the mechanism of establishing identity and + * comparing equality. + */ + private final String name; + + /** + * A brief description of the variable. This description is meant to be + * displayed to a user or simply provide a mechanism of documenting intent. + */ + private final String description; + + /** + * indicates that the value for this variable should be considered sensitive + * and protected whenever stored or represented + */ + private final boolean sensitive; + + /** + * Convenience constructor to create a descriptor based on name alone. To + * include additional parameters use Builder instead. + * + * @param name name used as unique identifier for this descriptor + */ + public VariableDescriptor(final String name) { + this(new Builder(name)); + } + + protected VariableDescriptor(final Builder builder) { + this.name = builder.name; + this.description = builder.description; + this.sensitive = builder.sensitive; + } + + @Override + public int compareTo(final VariableDescriptor o) { + if (o == null) { + return -1; + } + return getName().compareTo(o.getName()); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean isSensitive() { + return sensitive; + } + + @Override + public boolean equals(final Object other) { + if (other == null) { + return false; + } + if (this == other) { + return true; + } + if (!(other instanceof VariableDescriptor)) { + return false; + } + + final VariableDescriptor desc = (VariableDescriptor) other; + return this.name.equals(desc.name); + } + + @Override + public int hashCode() { + return 797 + this.name.hashCode() * 97; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + name + "]"; + } + + public static final class Builder { + + private String name = null; + private String description = ""; + private boolean sensitive = false; + + /** + * Establishes the unique identifier or key name of the variable. + * + * @param name of the property + */ + public Builder(final String name) { + if (null == name || name.trim().isEmpty()) { + throw new IllegalArgumentException("Name must not be null or empty"); + } + this.name = name.trim(); + } + + /** + * @param description of the variable + * @return the builder + */ + public Builder description(final String description) { + if (null != description) { + this.description = description; + } + return this; + } + + /** + * @param sensitive true if sensitive; false otherwise + * @return the builder + */ + public Builder sensitive(final boolean sensitive) { + this.sensitive = sensitive; + return this; + } + + /** + * @return a VariableDescriptor as configured + * + */ + public VariableDescriptor build() { + return new VariableDescriptor(this); + } + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/AbstractFlowRegistryClient.java b/src/main/java/org/apache/nifi/registry/flow/AbstractFlowRegistryClient.java new file mode 100644 index 0000000..eabb318 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/AbstractFlowRegistryClient.java
@@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.logging.ComponentLog; + +import javax.net.ssl.SSLContext; +import java.util.Optional; + +public abstract class AbstractFlowRegistryClient extends AbstractConfigurableComponent implements FlowRegistryClient { + + private volatile String identifier; + private volatile Optional<SSLContext> systemSslContext; + private volatile ComponentLog logger; + + @Override + public void initialize(final FlowRegistryClientInitializationContext context) { + this.identifier = context.getIdentifier(); + this.logger = context.getLogger(); + this.systemSslContext = context.getSystemSslContext(); + } + + @Override + public final String getIdentifier() { + return identifier; + } + + protected final ComponentLog getLogger() { + return logger; + } + + protected final Optional<SSLContext> getSystemSslContext() { + return systemSslContext; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/AuthenticationException.java b/src/main/java/org/apache/nifi/registry/flow/AuthenticationException.java new file mode 100644 index 0000000..a1d3baa --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/AuthenticationException.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class AuthenticationException extends FlowRegistryException { + + public AuthenticationException(final String message) { + super(message); + } + + public AuthenticationException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/AuthorizationException.java b/src/main/java/org/apache/nifi/registry/flow/AuthorizationException.java new file mode 100644 index 0000000..b803abf --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/AuthorizationException.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class AuthorizationException extends FlowRegistryException { + + public AuthorizationException(final String message) { + super(message); + } + + public AuthorizationException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/BucketLocation.java b/src/main/java/org/apache/nifi/registry/flow/BucketLocation.java new file mode 100644 index 0000000..58dddae --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/BucketLocation.java
@@ -0,0 +1,54 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.apache.nifi.registry.flow; + +/** + * Information for locating a bucket in a flow registry. + */ +public class BucketLocation { + + private String branch; + private String bucketId; + + public BucketLocation() { + + } + + public BucketLocation(final String branch, final String bucketId) { + this.branch = branch; + this.bucketId = bucketId; + } + + public String getBranch() { + return branch; + } + + public void setBranch(final String branch) { + this.branch = branch; + } + + public String getBucketId() { + return bucketId; + } + + public void setBucketId(final String bucketId) { + this.bucketId = bucketId; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowAlreadyExistsException.java b/src/main/java/org/apache/nifi/registry/flow/FlowAlreadyExistsException.java new file mode 100644 index 0000000..f31b869 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowAlreadyExistsException.java
@@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class FlowAlreadyExistsException extends FlowRegistryException { + + public FlowAlreadyExistsException(final String message) { + super(message); + } + + public FlowAlreadyExistsException(final String message, final Throwable cause) { + super(message, cause); + } +} +
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowLocation.java b/src/main/java/org/apache/nifi/registry/flow/FlowLocation.java new file mode 100644 index 0000000..c30f549 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowLocation.java
@@ -0,0 +1,45 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.apache.nifi.registry.flow; + +/** + * Information for locating a flow in a flow registry. + */ +public class FlowLocation extends BucketLocation { + + private String flowId; + + public FlowLocation() { + + } + + public FlowLocation(final String branch, final String bucketId, final String flowId) { + super(branch, bucketId); + this.flowId = flowId; + } + + public String getFlowId() { + return flowId; + } + + public void setFlowId(final String flowId) { + this.flowId = flowId; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBranch.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBranch.java new file mode 100644 index 0000000..e9d944c --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBranch.java
@@ -0,0 +1,52 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.apache.nifi.registry.flow; + +import java.util.Objects; + +public class FlowRegistryBranch { + + private String name; + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final FlowRegistryBranch that = (FlowRegistryBranch) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBucket.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBucket.java new file mode 100644 index 0000000..13ddc85 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryBucket.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class FlowRegistryBucket { + private String identifier; + private String name; + private String description; + private long createdTimestamp; + private FlowRegistryPermissions permissions; + + public String getIdentifier() { + return identifier; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public long getCreatedTimestamp() { + return createdTimestamp; + } + + public FlowRegistryPermissions getPermissions() { + return permissions; + } + + public void setIdentifier(final String identifier) { + this.identifier = identifier; + } + + public void setName(final String name) { + this.name = name; + } + + public void setDescription(final String description) { + this.description = description; + } + + public void setCreatedTimestamp(final long createdTimestamp) { + this.createdTimestamp = createdTimestamp; + } + + public void setPermissions(final FlowRegistryPermissions permissions) { + this.permissions = permissions; + } + +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClient.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClient.java new file mode 100644 index 0000000..860dfd8 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClient.java
@@ -0,0 +1,266 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import org.apache.nifi.components.ConfigurableComponent; + +import java.io.IOException; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * <p> + * Represents and external source, where flows might be stored and retrieved from. The interface provides + * the possibility to have multiple different types of registries backing NiFi. + * </p> + * + * <p> + * <code>FlowRegistryClient</code>s are discovered using Java's + * <code>ServiceLoader</code> mechanism. As a result, all implementations must + * follow these rules: + * + * <ul> + * <li>The implementation must implement this interface.</li> + * <li>The implementation must have a file named + * org.apache.nifi.registry.flow.FlowRegistryClient located within the jar's + * <code>META-INF/services</code> directory. This file contains a list of + * fully-qualified class names of all <code>FlowRegistryClient</code>s in the + * jar, one-per-line. + * <li>The implementation must support a default constructor.</li> + * </ul> + * + * <p> + * <code>FlowRegistryClient</code> instances are always considered "active" and approachable when the validation status is considered as valid. + * Therefore after initialize, implementations must expect incoming requests any time. + * </p> + * + * <p> + * The argument list of the request methods contain instances <code>FlowRegistryClientConfigurationContext</code>, which always contains the current + * state of the properties. Caching properties between method calls is not recommended. + * </p> + * + */ +public interface FlowRegistryClient extends ConfigurableComponent { + + String DEFAULT_BRANCH_NAME = "main"; + + void initialize(FlowRegistryClientInitializationContext context); + + /** + * Decides if the given location is applicable by the repository instance. The format depends on the implementation. + * + * @param context Configuration context. + * @param location The location of versioned flow to check. + * + * @return True in case of the given storage location is applicable, false otherwise. An applicable location does not + * mean that the flow specified by the location is stored in the registry. Depending on the implementation it might be + * merely a static check on the format of the location string. In case of uncertainty, NiFi tries to load the versioned + * flow from registries with true return value first. + */ + boolean isStorageLocationApplicable(FlowRegistryClientConfigurationContext context, String location); + + /** + * Indicates if the registry supports branching. + * + * If the registry does not supporting branching, then this method should return false and the registry client should use + * the default implementations of getBranches and getDefaultBranch. + * + * If the registry does support branching, then this method should return true and the registry client should use + * override getBranches and getDefaultBranch to provide appropriate implementations. + * + * @param context Configuration context + * @return true if the registry supports branching, false otherwise + */ + default boolean isBranchingSupported(final FlowRegistryClientConfigurationContext context) { + return false; + } + + /** + * Get the available branches. Should return at least one branch that matches the response of getDefaultBranch. + * + * @param context Configuration context + * @return the set of available branches + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + default Set<FlowRegistryBranch> getBranches(final FlowRegistryClientConfigurationContext context) throws FlowRegistryException, IOException { + return Set.of(getDefaultBranch(context)); + } + + /** + * Gets the default branch. Must return a non-null FlowRegistryBranch instance with a non-null name. + * The interface provides a default implementation which will return a branch named 'main'. + * + * @param context Configuration context + * @return the default branch + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + default FlowRegistryBranch getDefaultBranch(final FlowRegistryClientConfigurationContext context) throws FlowRegistryException, IOException { + final FlowRegistryBranch branch = new FlowRegistryBranch(); + branch.setName(DEFAULT_BRANCH_NAME); + return branch; + } + + /** + * Gets the buckets for the specified user. + * + * @param context Configuration context. + * @param branch the branch + * + * @return Buckets for this user. In case there are no available buckets for the user the result will be an empty set. + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + Set<FlowRegistryBucket> getBuckets(FlowRegistryClientConfigurationContext context, String branch) throws FlowRegistryException, IOException; + + /** + * Gets the bucket with the given id. + * + * @param context Configuration context. + * @param bucketLocation The location of the bucket. + * @return The bucket with the given id. + * + * @throws NoSuchBucketException If there is no bucket in the Registry with the given id. + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + FlowRegistryBucket getBucket(FlowRegistryClientConfigurationContext context, BucketLocation bucketLocation) throws FlowRegistryException, IOException; + + /** + * Registers the given RegisteredFlow into the Flow Registry. + * + * @param context Configuration context. + * @param flow The RegisteredFlow to add to the Registry. + * + * @return The fully populated RegisteredFlow + * + * @throws FlowAlreadyExistsException If a Flow with the given identifier already exists in the registry. + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + RegisteredFlow registerFlow(FlowRegistryClientConfigurationContext context, RegisteredFlow flow) throws FlowRegistryException, IOException; + + /** + * Deletes the specified flow from the Flow Registry. + * + * @param context Configuration context. + * @param flowLocation the location of the flow + * + * @return The deleted Flow. + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + RegisteredFlow deregisterFlow(FlowRegistryClientConfigurationContext context, FlowLocation flowLocation) throws FlowRegistryException, IOException; + + /** + * Retrieves a flow by bucket id and Flow id. + * + * @param context Configuration context. + * @param flowLocation the location of the flow + * + * @return The Flow for the given bucket and Flow id's. + * + * @throws NoSuchFlowException If there is no Flow in the bucket with the given id. + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + RegisteredFlow getFlow(FlowRegistryClientConfigurationContext context, FlowLocation flowLocation) throws FlowRegistryException, IOException; + + /** + * Retrieves the set of all Flows for the specified bucket. + * + * @param context Configuration context. + * @param bucketLocation the location of the bucket + * + * @return The set of all Flows from the specified bucket. In case there are no available flows in the bucket the result will be an empty set. + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + Set<RegisteredFlow> getFlows(FlowRegistryClientConfigurationContext context, BucketLocation bucketLocation) throws FlowRegistryException, IOException; + + /** + * Retrieves the contents of the flow snapshot with the given Bucket id, Flow id, and version, from the Registry. + * + * @param context Configuration context. + * @param flowVersionLocation the location of the flow version + * + * @return The contents of the Flow from the Flow Registry. + * + * @throws NoSuchFlowVersionException If there is no version of the Flow with the given version number. + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + RegisteredFlowSnapshot getFlowContents(FlowRegistryClientConfigurationContext context, FlowVersionLocation flowVersionLocation) throws FlowRegistryException, IOException; + + /** + * Adds the given snapshot to the Flow Registry for the given Flow. + * + * @param context Configuration context. + * @param flowSnapshot The flow snapshot to register. + * @param action The register action + * + * @return The flow snapshot. + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + RegisteredFlowSnapshot registerFlowSnapshot(FlowRegistryClientConfigurationContext context, RegisteredFlowSnapshot flowSnapshot, RegisterAction action) throws FlowRegistryException, IOException; + + /** + * Retrieves the set of all versions of the specified flow. + * + * @param context Configuration context. + * @param flowLocation the location of the flow + * + * @return The set of all versions of the specified flow. In case there are no available versions for the specified flow the result will be an empty set. + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + Set<RegisteredFlowSnapshotMetadata> getFlowVersions(FlowRegistryClientConfigurationContext context, FlowLocation flowLocation) throws FlowRegistryException, IOException; + + /** + * Returns the latest (most recent) version of the Flow in the Flow Registry for the given bucket and Flow. + * + * @param context Configuration context. + * @param flowLocation the location of the flow + * + * @return an Optional containing the latest version of the flow, or empty if no versions exist for the flow + * + * @throws FlowRegistryException If an issue happens during processing the request. + * @throws IOException If there is issue with the communication between NiFi and the Flow Registry. + */ + Optional<String> getLatestVersion(FlowRegistryClientConfigurationContext context, FlowLocation flowLocation) throws FlowRegistryException, IOException; + + /** + * Generates the id for registering a flow. + * + * @param flowName the name of the flow + * @return the generated id + */ + default String generateFlowId(final String flowName) { + return UUID.randomUUID().toString(); + } + +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientConfigurationContext.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientConfigurationContext.java new file mode 100644 index 0000000..f0482ad --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientConfigurationContext.java
@@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import org.apache.nifi.context.PropertyContext; + +import java.util.Optional; + +public interface FlowRegistryClientConfigurationContext extends PropertyContext { + Optional<String> getNiFiUserIdentity(); +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientInitializationContext.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientInitializationContext.java new file mode 100644 index 0000000..06baef6 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryClientInitializationContext.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import org.apache.nifi.logging.ComponentLog; + +import javax.net.ssl.SSLContext; +import java.util.Optional; + +public interface FlowRegistryClientInitializationContext { + + String getIdentifier(); + + ComponentLog getLogger(); + + Optional<SSLContext> getSystemSslContext(); +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryException.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryException.java new file mode 100644 index 0000000..f137c28 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryException.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +/** + * Indicates an error interacting with the NiFi registry for a reason other than IOException. + */ +public class FlowRegistryException extends Exception { + + public FlowRegistryException(final String message) { + super(message); + } + + public FlowRegistryException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryInvalidException.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryInvalidException.java new file mode 100644 index 0000000..4278ead --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryInvalidException.java
@@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import java.util.List; + +public class FlowRegistryInvalidException extends FlowRegistryException { + public FlowRegistryInvalidException(final List<String> validationErrors) { + super("The flow registry client instance has the following validation errors: " + String.join(", ", validationErrors)); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPermissions.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPermissions.java new file mode 100644 index 0000000..28c7d8b --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPermissions.java
@@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class FlowRegistryPermissions { + private boolean canRead; + private boolean canWrite; + private boolean canDelete; + + public boolean getCanRead() { + return canRead; + } + + public boolean getCanWrite() { + return canWrite; + } + + public boolean getCanDelete() { + return canDelete; + } + + public void setCanRead(final boolean canRead) { + this.canRead = canRead; + } + + public void setCanWrite(final boolean canWrite) { + this.canWrite = canWrite; + } + + public void setCanDelete(final boolean canDelete) { + this.canDelete = canDelete; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPreCommitException.java b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPreCommitException.java new file mode 100644 index 0000000..46e4f43 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowRegistryPreCommitException.java
@@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class FlowRegistryPreCommitException extends FlowRegistryException { + public FlowRegistryPreCommitException(final String message) { + super(message); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/FlowVersionLocation.java b/src/main/java/org/apache/nifi/registry/flow/FlowVersionLocation.java new file mode 100644 index 0000000..3e4a482 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/FlowVersionLocation.java
@@ -0,0 +1,67 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.apache.nifi.registry.flow; + +import java.util.Objects; + +/** + * Information for locating a flow version in a flow registry. + */ +public class FlowVersionLocation extends FlowLocation { + + private String version; + + public FlowVersionLocation() { + + } + + public FlowVersionLocation(final String branch, final String bucketId, final String flowId, final String version) { + super(branch, bucketId, flowId); + this.version = version; + } + + public String getVersion() { + return version; + } + + public void setVersion(final String version) { + this.version = version; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final FlowVersionLocation that = (FlowVersionLocation) o; + return Objects.equals(getBranch(), that.getBranch()) + && Objects.equals(getBucketId(), that.getBucketId()) + && Objects.equals(getFlowId(), that.getFlowId()) + && Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(getBranch(), getBucketId(), getFlowId(), version); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/NoSuchBucketException.java b/src/main/java/org/apache/nifi/registry/flow/NoSuchBucketException.java new file mode 100644 index 0000000..c498332 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/NoSuchBucketException.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class NoSuchBucketException extends FlowRegistryException { + + public NoSuchBucketException(final String message) { + super(message); + } + + public NoSuchBucketException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowException.java b/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowException.java new file mode 100644 index 0000000..e36a591 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowException.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class NoSuchFlowException extends FlowRegistryException { + + public NoSuchFlowException(final String message) { + super(message); + } + + public NoSuchFlowException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowVersionException.java b/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowVersionException.java new file mode 100644 index 0000000..94d8f34 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/NoSuchFlowVersionException.java
@@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class NoSuchFlowVersionException extends FlowRegistryException { + + public NoSuchFlowVersionException(final String message) { + super(message); + } + + public NoSuchFlowVersionException(final String message, final Throwable cause) { + super(message, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/RegisterAction.java b/src/main/java/org/apache/nifi/registry/flow/RegisterAction.java new file mode 100644 index 0000000..593a302 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/RegisterAction.java
@@ -0,0 +1,25 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.apache.nifi.registry.flow; + +public enum RegisterAction { + COMMIT, + FORCE_COMMIT; +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/RegisteredFlow.java b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlow.java new file mode 100644 index 0000000..c8d39d9 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlow.java
@@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class RegisteredFlow { + private String identifier; + private String name; + private String description; + private String branch; + private String bucketIdentifier; + private String bucketName; + private long createdTimestamp; + private long lastModifiedTimestamp; + private FlowRegistryPermissions permissions; + private long versionCount; + private RegisteredFlowVersionInfo versionInfo; + + public String getIdentifier() { + return identifier; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getBranch() { + return branch; + } + + public void setBranch(final String branch) { + this.branch = branch; + } + + public String getBucketIdentifier() { + return bucketIdentifier; + } + + public String getBucketName() { + return bucketName; + } + + public long getCreatedTimestamp() { + return createdTimestamp; + } + + public long getLastModifiedTimestamp() { + return lastModifiedTimestamp; + } + + public FlowRegistryPermissions getPermissions() { + return permissions; + } + + public long getVersionCount() { + return versionCount; + } + + public RegisteredFlowVersionInfo getVersionInfo() { + return versionInfo; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setBucketIdentifier(String bucketIdentifier) { + this.bucketIdentifier = bucketIdentifier; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public void setCreatedTimestamp(long createdTimestamp) { + this.createdTimestamp = createdTimestamp; + } + + public void setLastModifiedTimestamp(long lastModifiedTimestamp) { + this.lastModifiedTimestamp = lastModifiedTimestamp; + } + + public void setPermissions(FlowRegistryPermissions permissions) { + this.permissions = permissions; + } + + public void setVersionCount(long versionCount) { + this.versionCount = versionCount; + } + + public void setVersionInfo(RegisteredFlowVersionInfo versionInfo) { + this.versionInfo = versionInfo; + } + +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshot.java b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshot.java new file mode 100644 index 0000000..79bf1a8 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshot.java
@@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +import org.apache.nifi.flow.ExternalControllerServiceReference; +import org.apache.nifi.flow.ParameterProviderReference; +import org.apache.nifi.flow.VersionedParameterContext; +import org.apache.nifi.flow.VersionedProcessGroup; + +import java.util.Map; + +public class RegisteredFlowSnapshot { + private RegisteredFlowSnapshotMetadata snapshotMetadata; + private RegisteredFlow flow; + private FlowRegistryBucket bucket; + private VersionedProcessGroup flowContents; + private Map<String, ExternalControllerServiceReference> externalControllerServices; + private Map<String, VersionedParameterContext> parameterContexts; + private String flowEncodingVersion; + private Map<String, ParameterProviderReference> parameterProviders; + private boolean latest; + + public RegisteredFlowSnapshotMetadata getSnapshotMetadata() { + return snapshotMetadata; + } + + public RegisteredFlow getFlow() { + return flow; + } + + public FlowRegistryBucket getBucket() { + return bucket; + } + + public VersionedProcessGroup getFlowContents() { + return flowContents; + } + + public Map<String, ExternalControllerServiceReference> getExternalControllerServices() { + return externalControllerServices; + } + + public Map<String, VersionedParameterContext> getParameterContexts() { + return parameterContexts; + } + + public String getFlowEncodingVersion() { + return flowEncodingVersion; + } + + public boolean isLatest() { + return latest; + } + + public void setSnapshotMetadata(final RegisteredFlowSnapshotMetadata snapshotMetadata) { + this.snapshotMetadata = snapshotMetadata; + } + + public void setFlow(RegisteredFlow flow) { + this.flow = flow; + } + + public void setBucket(FlowRegistryBucket bucket) { + this.bucket = bucket; + } + + public void setFlowContents(VersionedProcessGroup flowContents) { + this.flowContents = flowContents; + } + + public void setExternalControllerServices(final Map<String, ExternalControllerServiceReference> externalControllerServices) { + this.externalControllerServices = externalControllerServices; + } + + public void setParameterContexts(final Map<String, VersionedParameterContext> parameterContexts) { + this.parameterContexts = parameterContexts; + } + + public void setFlowEncodingVersion(String flowEncodingVersion) { + this.flowEncodingVersion = flowEncodingVersion; + } + + public Map<String, ParameterProviderReference> getParameterProviders() { + return parameterProviders; + } + + public void setParameterProviders(final Map<String, ParameterProviderReference> parameterProviders) { + this.parameterProviders = parameterProviders; + } + + public void setLatest(final boolean latest) { + this.latest = latest; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshotMetadata.java b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshotMetadata.java new file mode 100644 index 0000000..5f7864d --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowSnapshotMetadata.java
@@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class RegisteredFlowSnapshotMetadata { + private String branch; + private String bucketIdentifier; + private String flowIdentifier; + private String version; + private long timestamp; + private String author; + private String comments; + + public String getBranch() { + return branch; + } + + public String getBucketIdentifier() { + return bucketIdentifier; + } + + public String getFlowIdentifier() { + return flowIdentifier; + } + + public String getVersion() { + return version; + } + + public long getTimestamp() { + return timestamp; + } + + public String getAuthor() { + return author; + } + + public String getComments() { + return comments; + } + + public void setBranch(final String branch) { + this.branch = branch; + } + + public void setBucketIdentifier(String bucketIdentifier) { + this.bucketIdentifier = bucketIdentifier; + } + + public void setFlowIdentifier(String flowIdentifier) { + this.flowIdentifier = flowIdentifier; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setComments(String comments) { + this.comments = comments; + } +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowVersionInfo.java b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowVersionInfo.java new file mode 100644 index 0000000..58e058a --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/RegisteredFlowVersionInfo.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.flow; + +public class RegisteredFlowVersionInfo { + + private long version; + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + +}
diff --git a/src/main/java/org/apache/nifi/registry/flow/VersionedFlowState.java b/src/main/java/org/apache/nifi/registry/flow/VersionedFlowState.java new file mode 100644 index 0000000..152dcd8 --- /dev/null +++ b/src/main/java/org/apache/nifi/registry/flow/VersionedFlowState.java
@@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.registry.flow; + +public enum VersionedFlowState { + + /** + * We are unable to communicate with the Flow Registry in order to determine the appropriate state + */ + SYNC_FAILURE("Failed to communicate with Flow Registry"), + + /** + * This Process Group (or a child/descendant Process Group that is not itself under Version Control) + * is on the latest version of the Versioned Flow, but is different than the Versioned Flow that is + * stored in the Flow Registry. + */ + LOCALLY_MODIFIED("Local changes have been made"), + + /** + * This Process Group has not been modified since it was last synchronized with the Flow Registry, but + * the Flow Registry has a newer version of the flow than what is contained in this Process Group. + */ + STALE("A newer version of this flow is available"), + + /** + * This Process Group (or a child/descendant Process Group that is not itself under Version Control) + * has been modified since it was last synchronized with the Flow Registry, and the Flow Registry has + * a newer version of the flow than what is contained in this Process Group. + */ + LOCALLY_MODIFIED_AND_STALE("Local changes have been made and a newer version of this flow is available"), + + /** + * This Process Group and all child/descendant Process Groups are on the latest version of the flow in + * the Flow Registry and have no local modifications. + */ + UP_TO_DATE("Flow version is current"); + + + private final String description; + + VersionedFlowState(final String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/AbstractReportingTask.java b/src/main/java/org/apache/nifi/reporting/AbstractReportingTask.java new file mode 100644 index 0000000..1cf214e --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/AbstractReportingTask.java
@@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.components.AbstractConfigurableComponent; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.logging.ComponentLog; + +import java.util.concurrent.TimeUnit; + +public abstract class AbstractReportingTask extends AbstractConfigurableComponent implements ReportingTask { + + private String identifier; + private String name; + private long schedulingNanos; + private ControllerServiceLookup serviceLookup; + private ComponentLog logger; + private NodeTypeProvider nodeTypeProvider; + + @Override + public final void initialize(final ReportingInitializationContext config) throws InitializationException { + identifier = config.getIdentifier(); + logger = config.getLogger(); + name = config.getName(); + schedulingNanos = config.getSchedulingPeriod(TimeUnit.NANOSECONDS); + serviceLookup = config.getControllerServiceLookup(); + nodeTypeProvider = config.getNodeTypeProvider(); + + init(config); + } + + /** + * @return the {@link ControllerServiceLookup} that was passed to the + * {@link #initialize(ReportingInitializationContext)} method + */ + protected final ControllerServiceLookup getControllerServiceLookup() { + return serviceLookup; + } + + /** + * @return the {@link NodeTypeProvider} that was passed to the + * {@link #initialize(ReportingInitializationContext)} method + */ + protected final NodeTypeProvider getNodeTypeProvider() { + return nodeTypeProvider; + } + + /** + * @return the identifier of this Reporting Task + */ + @Override + public String getIdentifier() { + return identifier; + } + + /** + * @return the name of this Reporting Task + */ + protected String getName() { + return name; + } + + /** + * Sets various component information using the given context + * @param context the context to use for this reporting task + */ + @OnScheduled + public void setComponentInfo(ConfigurationContext context) { + this.name = context.getName(); + } + + /** + * @param timeUnit of scheduling period + * @return the amount of times that elapses between the moment that this + * ReportingTask finishes its invocation of + * {@link #onTrigger(ReportingContext)} and the next time that + * {@link #onTrigger(ReportingContext)} is called. + */ + protected long getSchedulingPeriod(final TimeUnit timeUnit) { + return timeUnit.convert(schedulingNanos, TimeUnit.NANOSECONDS); + } + + /** + * Provides a mechanism by which subclasses can perform initialization of + * the Reporting Task before it is scheduled to be run + * + * @param config context + * @throws InitializationException if failure to init + */ + protected void init(final ReportingInitializationContext config) throws InitializationException { + } + + /** + * @return the logger that has been provided to the component by the + * framework in its initialize method + */ + protected ComponentLog getLogger() { + return logger; + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/Bulletin.java b/src/main/java/org/apache/nifi/reporting/Bulletin.java new file mode 100644 index 0000000..e00ad3b --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/Bulletin.java
@@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import java.util.Date; + +/** + * A Bulletin is a construct that represents a message that is to be displayed + * to users to notify of a specific (usually fleeting) event. + */ +public abstract class Bulletin implements Comparable<Bulletin> { + + private final Date timestamp; + private final long id; + private String nodeAddress; + private String level; + private String category; + private String message; + + private String groupId; + private String groupName; + private String groupPath; + private String sourceId; + private String sourceName; + private ComponentType sourceType; + private String flowFileUuid; + + protected Bulletin(final long id) { + this.timestamp = new Date(); + this.id = id; + } + + public String getNodeAddress() { + return nodeAddress; + } + + public void setNodeAddress(String nodeAddress) { + this.nodeAddress = nodeAddress; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public long getId() { + return id; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getGroupPath() { + return groupPath; + } + + public void setGroupPath(String groupPath) { + this.groupPath = groupPath; + } + + public String getSourceId() { + return sourceId; + } + + public void setSourceId(String sourceId) { + this.sourceId = sourceId; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public ComponentType getSourceType() { + return sourceType; + } + + public void setSourceType(ComponentType sourceType) { + this.sourceType = sourceType; + } + + public String getFlowFileUuid() { + return flowFileUuid; + } + + public void setFlowFileUuid(String flowFileUuid) { + this.flowFileUuid = flowFileUuid; + } + + @Override + public String toString() { + return "Bulletin{" + "id=" + id + ", message=" + message + ", sourceName=" + sourceName + ", sourceType=" + sourceType + '}'; + } + + @Override + public int compareTo(Bulletin b) { + if (b == null) { + return -1; + } + + return -Long.compare(getId(), b.getId()); + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/BulletinQuery.java b/src/main/java/org/apache/nifi/reporting/BulletinQuery.java new file mode 100644 index 0000000..aae2eff --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/BulletinQuery.java
@@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import java.util.regex.Pattern; + +public class BulletinQuery { + + private final ComponentType sourceType; + private final Pattern sourceIdPattern; + private final Pattern groupIdPattern; + private final Pattern namePattern; + private final Pattern messagePattern; + private final Long after; + private final Integer limit; + + private BulletinQuery(final Builder builder) { + this.sourceType = builder.sourceType; + this.sourceIdPattern = builder.sourceIdPattern == null ? null : Pattern.compile(builder.sourceIdPattern); + this.groupIdPattern = builder.groupIdPattern == null ? null : Pattern.compile(builder.groupIdPattern); + this.namePattern = builder.namePattern == null ? null : Pattern.compile(builder.namePattern); + this.messagePattern = builder.messagePattern == null ? null : Pattern.compile(builder.messagePattern); + this.after = builder.after; + this.limit = builder.limit; + } + + public ComponentType getSourceType() { + return sourceType; + } + + public Pattern getSourceIdPattern() { + return sourceIdPattern; + } + + public Pattern getGroupIdPattern() { + return groupIdPattern; + } + + public Pattern getNamePattern() { + return namePattern; + } + + public Pattern getMessagePattern() { + return messagePattern; + } + + public Long getAfter() { + return after; + } + + public Integer getLimit() { + return limit; + } + + public static class Builder { + + private ComponentType sourceType; + private String sourceIdPattern; + private String groupIdPattern; + private String namePattern; + private String messagePattern; + private Long after; + private Integer limit; + + public Builder after(Long after) { + this.after = after; + return this; + } + + public Builder groupIdMatches(String groupId) { + this.groupIdPattern = groupId; + return this; + } + + public Builder sourceIdMatches(String sourceId) { + this.sourceIdPattern = sourceId; + return this; + } + + public Builder sourceType(ComponentType sourceType) { + this.sourceType = sourceType; + return this; + } + + public Builder nameMatches(String name) { + this.namePattern = name; + return this; + } + + public Builder messageMatches(String message) { + this.messagePattern = message; + return this; + } + + public Builder limit(Integer limit) { + this.limit = limit; + return this; + } + + public BulletinQuery build() { + return new BulletinQuery(this); + } + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/BulletinRepository.java b/src/main/java/org/apache/nifi/reporting/BulletinRepository.java new file mode 100644 index 0000000..1bf5768 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/BulletinRepository.java
@@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import java.util.List; + +/** + * The BulletinRepository provides a place to store and retrieve + * {@link Bulletin}s that have been created by the NiFi Framework and the + * Components that are running within the Framework. + */ +public interface BulletinRepository { + + int MAX_BULLETINS_PER_COMPONENT = 5; + int MAX_BULLETINS_FOR_CONTROLLER = 10; + + /** + * Adds a Bulletin to the repository. + * + * @param bulletin to add + */ + void addBulletin(Bulletin bulletin); + + /** + * @return the capacity for the number of bulletins for the controller + */ + int getControllerBulletinCapacity(); + + /** + * @return the capacity for the number of bulletins per component + */ + int getComponentBulletinCapacity(); + + /** + * Finds Bulletin's that meet the specified query. + * + * @param bulletinQuery indicates which bulletins are of interest + * @return bulletins that met the query + */ + List<Bulletin> findBulletins(BulletinQuery bulletinQuery); + + /** + * Finds all bulletins for the specified source component. + * + * @param sourceId id of the source + * @return bulletins for the given source + */ + List<Bulletin> findBulletinsForSource(String sourceId); + + /** + * Finds all bulletins for the specified source component that resides in the given group. While the + * {@link #findBulletinsForSource(String)} method may be used, this method is preferred when the ID of the group + * is known, as this is far more efficient. + * + * @param sourceId the id of the source component + * @param groupId the id of the process group + * @return bulletins for the given source + */ + List<Bulletin> findBulletinsForSource(String sourceId, String groupId); + + /** + * Finds all bulletins for the specified group. + * + * @param groupId id of the group + * @return bulletins for the given group + */ + List<Bulletin> findBulletinsForGroupBySource(String groupId); + + /** + * Finds all bulletins for the specified group. + * + * @param groupId id of the group + * @param maxPerComponent max responses wanted + * @return bulletins found + */ + List<Bulletin> findBulletinsForGroupBySource(String groupId, int maxPerComponent); + + /** + * @return all bulletins for the controller + */ + List<Bulletin> findBulletinsForController(); + + /** + * Finds all bulletins for the controller; + * + * @param max limits the number of responses + * @return all bulletins for the controller + */ + List<Bulletin> findBulletinsForController(int max); + + /** + * @return the max ID of any bulletin that has been added, or -1 if no bulletins have been added + */ + long getMaxBulletinId(); +}
diff --git a/src/main/java/org/apache/nifi/reporting/ComponentType.java b/src/main/java/org/apache/nifi/reporting/ComponentType.java new file mode 100644 index 0000000..95cec81 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/ComponentType.java
@@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +/** + * An Enumeration for indicating which type of component a Bulletin is associated with + */ +public enum ComponentType { + + /** + * Bulletin is associated with a Processor + */ + PROCESSOR, + + /** + * Bulletin is associated with a Remote Process Group + */ + REMOTE_PROCESS_GROUP, + + /** + * Bulletin is associated with an Input Port + */ + INPUT_PORT, + + /** + * Bulletin is associated with an Output Port + */ + OUTPUT_PORT, + + /** + * Bulletin is associated with a Reporting Task + */ + REPORTING_TASK, + + /** + * Bulletin is associated with a Flow Analysis Rule + */ + FLOW_ANALYSIS_RULE, + + /** + * Bulletin is associated with a Process Group + */ + PROCESS_GROUP, + + /** + * Bulletin is associated with a Parameter Provider + */ + PARAMETER_PROVIDER, + + /** + * Bulletin is associated with a Controller Service + */ + CONTROLLER_SERVICE, + + /** + * Bulletin is a system-level bulletin, associated with the Flow Controller + */ + FLOW_CONTROLLER, + + /** + * Bulletin is associated with a Flow Registry Client + */ + FLOW_REGISTRY_CLIENT; +}
diff --git a/src/main/java/org/apache/nifi/reporting/EventAccess.java b/src/main/java/org/apache/nifi/reporting/EventAccess.java new file mode 100644 index 0000000..3e2c907 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/EventAccess.java
@@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.action.Action; +import org.apache.nifi.controller.status.ProcessGroupStatus; +import org.apache.nifi.diagnostics.StorageUsage; +import org.apache.nifi.provenance.ProvenanceEventRecord; +import org.apache.nifi.provenance.ProvenanceEventRepository; + +public interface EventAccess { + + /** + * @return the status for all components in this Controller + */ + ProcessGroupStatus getControllerStatus(); + + /** + * @param groupId Process Group Identifier + * @return the status of all components in the specified group. + */ + ProcessGroupStatus getGroupStatus(final String groupId); + + /** + * Convenience method to obtain Provenance Events starting with (and + * including) the given ID. If no event exists with that ID, the first event + * to be returned will be have an ID greater than <code>firstEventId</code>. + * + * @param firstEventId the ID of the first event to obtain + * @param maxRecords the maximum number of records to obtain + * @return event records matching query + * @throws java.io.IOException if unable to get records + */ + List<ProvenanceEventRecord> getProvenanceEvents(long firstEventId, final int maxRecords) throws IOException; + + /** + * @return the Provenance Event Repository + */ + ProvenanceEventRepository getProvenanceRepository(); + + /** + * Obtains flow changes starting with (and including) the given action ID. If no action + * exists with that ID, the first action to be returned will have an ID greater than + * <code>firstActionId</code>. + * + * @param firstActionId the id of the first action to obtain + * @param maxActions the maximum number of actions to obtain + * @return actions with ids greater than or equal to firstActionID, up to the max number of actions + */ + List<Action> getFlowChanges(int firstActionId, final int maxActions); + + /** + * Returns the total number of bytes read by this instance (at the root process group level, i.e. all events) since the instance started + * + * @return the total number of bytes read by this instance + */ + long getTotalBytesRead(); + + /** + * Returns the total number of bytes written by this instance (at the root process group level, i.e. all events) since the instance started + * + * @return the total number of bytes written by this instance + */ + long getTotalBytesWritten(); + + /** + * Returns the total number of bytes sent by this instance (at the root process group level) since the instance started + * + * @return the total number of bytes sent by this instance + */ + long getTotalBytesSent(); + + /** + * Returns the total number of bytes received by this instance (at the root process group level) since the instance started + * + * @return the total number of bytes received by this instance + */ + long getTotalBytesReceived(); + + /** + * Returns the storage usage of all provenance repositories + * @return the map of all the storage usage + */ + Map<String, StorageUsage> getProvenanceRepositoryStorageUsage(); + + /** + * Returns the storage usage of all content repositories + * @return the map of all the storage usage + */ + Map<String, StorageUsage> getContentRepositoryStorageUsage(); + + /** + * Returns the storage usage of the flow file repository + * @return the storage usage + */ + StorageUsage getFlowFileRepositoryStorageUsage(); + +}
diff --git a/src/main/java/org/apache/nifi/reporting/InitializationException.java b/src/main/java/org/apache/nifi/reporting/InitializationException.java new file mode 100644 index 0000000..c2f21b4 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/InitializationException.java
@@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +public class InitializationException extends Exception { + + public InitializationException(String explanation) { + super(explanation); + } + + public InitializationException(Throwable cause) { + super(cause); + } + + public InitializationException(String explanation, Throwable cause) { + super(explanation, cause); + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/ReportingContext.java b/src/main/java/org/apache/nifi/reporting/ReportingContext.java new file mode 100644 index 0000000..85cf844 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/ReportingContext.java
@@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import java.util.Map; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.state.StateManager; +import org.apache.nifi.context.PropertyContext; +import org.apache.nifi.controller.ControllerServiceLookup; + +/** + * This interface provides a bridge between the NiFi Framework and a + * {@link ReportingTask}. This context allows a ReportingTask to access + * statistics, metrics, and monitoring information, as well as configuration + * supplied by the user. + */ +public interface ReportingContext extends PropertyContext { + + /** + * @return a Map of all known {@link PropertyDescriptor}s to their + * configured properties. This Map will contain a <code>null</code> for any + * Property that has not been configured by the user, even if the + * PropertyDescriptor has a default value + */ + Map<PropertyDescriptor, String> getProperties(); + + /** + * @return the {@link EventAccess} object that can be used to obtain + * information about specific events and reports that have happened + */ + EventAccess getEventAccess(); + + /** + * @return the {@link BulletinRepository} that can be used to analyze + * Bulletins that have been emitted and register new Bulletins + */ + BulletinRepository getBulletinRepository(); + + /** + * Creates a controller-level {@link Bulletin} with the given category, severity + * level, and message, so that the Bulletin can be added to the + * {@link BulletinRepository}. Access to this bulletin will be enforce through + * permissions on the controller. + * + * @param category of bulletin + * @param severity of bulletin + * @param message of bulletin + * @return new bulletin + */ + Bulletin createBulletin(String category, Severity severity, String message); + + /** + * Creates a {@link Bulletin} for the component with the specified + * identifier. + * + * @param componentId the ID of the component + * @param category the name of the bulletin's category + * @param severity the severity level of the bulletin + * @param message the bulletin's message + * @return new bulletin + */ + Bulletin createBulletin(String componentId, String category, Severity severity, String message); + + /** + * @return the {@link ControllerServiceLookup} which can be used to obtain + * Controller Services + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @return the StateManager that can be used to store and retrieve state for this component + */ + StateManager getStateManager(); + + /** + * @return <code>true</code> if this instance of NiFi is configured to be part of a cluster, <code>false</code> + * if this instance of NiFi is a standalone instance + */ + boolean isClustered(); + + /** + * @return the ID of this node in the cluster, or <code>null</code> if either this node is not clustered or the Node Identifier + * has not yet been established + */ + String getClusterNodeIdentifier(); + + /** + * @return true if reporting analytics (connection status predictions, e.g.) are enabled, false otherwise + */ + default boolean isAnalyticsEnabled() { + return false; + } +}
diff --git a/src/main/java/org/apache/nifi/reporting/ReportingInitializationContext.java b/src/main/java/org/apache/nifi/reporting/ReportingInitializationContext.java new file mode 100644 index 0000000..978b421 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/ReportingInitializationContext.java
@@ -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. + */ +package org.apache.nifi.reporting; + +import java.util.concurrent.TimeUnit; +import org.apache.nifi.controller.ControllerServiceLookup; +import org.apache.nifi.controller.NodeTypeProvider; +import org.apache.nifi.kerberos.KerberosContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.scheduling.SchedulingStrategy; + +/** + * A ReportingConfiguration provides configuration information to a + * ReportingTask at the time of initialization + */ +public interface ReportingInitializationContext extends KerberosContext { + + /** + * @return the identifier for this ReportingTask + */ + String getIdentifier(); + + /** + * @return the configured name for this ReportingTask + */ + String getName(); + + /** + * Returns the amount of time, in the given {@link TimeUnit} that will + * elapsed between the return of one execution of the + * {@link ReportingTask}'s + * {@link ReportingTask#onTrigger(ReportingContext) onTrigger} method and + * the time at which the method is invoked again. This method will return + * <code>-1L</code> if the Scheduling Strategy is not set to + * {@link SchedulingStrategy#TIMER_DRIVEN} + * + * @param timeUnit unit of time for scheduling + * @return period of time + */ + long getSchedulingPeriod(TimeUnit timeUnit); + + /** + * @return the {@link ControllerServiceLookup} which can be used to obtain + * Controller Services + */ + ControllerServiceLookup getControllerServiceLookup(); + + /** + * @return a String representation of the scheduling period + */ + String getSchedulingPeriod(); + + /** + * @return the {@link SchedulingStrategy} that is used to trigger the task + * to run + */ + SchedulingStrategy getSchedulingStrategy(); + + /** + * @return a logger that can be used to log important events in a standard + * way and generate bulletins when appropriate + */ + ComponentLog getLogger(); + + /** + * @return the {@link NodeTypeProvider} which can be used to detect the node + * type of this NiFi instance. + * @since Apache NiFi 1.5.0 + */ + NodeTypeProvider getNodeTypeProvider(); + +}
diff --git a/src/main/java/org/apache/nifi/reporting/ReportingTask.java b/src/main/java/org/apache/nifi/reporting/ReportingTask.java new file mode 100644 index 0000000..27855f7 --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/ReportingTask.java
@@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored; +import org.apache.nifi.components.ConfigurableComponent; +import org.apache.nifi.migration.PropertyConfiguration; + +/** + * Defines a task that is responsible for reporting status information to + * external destinations. All implementations of this class must have a default + * constructor. + * + * <p> + * <code>ReportingTask</code>s are discovered using Java's + * <code>ServiceLoader</code> mechanism. As a result, all implementations must + * follow these rules: + * + * <ul> + * <li>The implementation must implement this interface.</li> + * <li>The implementation must have a file named + * org.apache.nifi.reporting.ReportingTask located within the jar's + * <code>META-INF/services</code> directory. This file contains a list of + * fully-qualified class names of all <code>ReportingTask</code>s in the jar, + * one-per-line. + * <li>The implementation must support a default constructor.</li> + * </ul> + * + * <p> + * ReportingTasks are scheduled on a delayed interval with a single thread. + * Therefore, implementations are not required to be thread-safe. + * </p> + * + * <p> + * ReportingTasks may choose to annotate a method with the + * {@link OnConfigurationRestored @OnConfigurationRestored} annotation. If this is done, that method + * will be invoked after all properties have been set for the ReportingTask and + * before it is scheduled to run. If the method throws an Exception, the + * ReportingTask will be Administratively yielded and will not run for the + * configured period of time. + * </p> + */ +public interface ReportingTask extends ConfigurableComponent { + + /** + * Provides the Reporting Task with access to objects that may be of use + * throughout the life of the service + * + * @param config of initialization context + * @throws org.apache.nifi.reporting.InitializationException if unable to init + */ + void initialize(ReportingInitializationContext config) throws InitializationException; + + /** + * This method is called on a scheduled interval to allow the Reporting Task + * to perform its tasks. + * + * @param context reporting context + */ + void onTrigger(ReportingContext context); + + /** + * Indicates whether this reporting task, configured with the given {@link ReportingContext}, stores state. + * @param context provides access to convenience methods for obtaining property values + * @return True if this reporting task stores state + */ + default boolean isStateful(ReportingContext context) { + return this.getClass().isAnnotationPresent(Stateful.class); + } + + /** + * <p> + * Allows for the migration of an old property configuration to a new configuration. This allows the Reporting Task to evolve over time, + * as it allows properties to be renamed, removed, or reconfigured. + * </p> + * + * <p> + * This method is called only when a Reporting Task is restored from a previous configuration. For example, when NiFi is restarted and the + * flow is restored from disk, when a previously configured flow is imported (e.g., from a JSON file that was exported or a NiFi Registry), + * or when a node joins a cluster and inherits a flow that has a new Reporting Task. Once called, the method will not be invoked again for this + * Reporting Task until NiFi is restarted. + * </p> + * + * @param config the current property configuration + */ + default void migrateProperties(PropertyConfiguration config) { + } + +}
diff --git a/src/main/java/org/apache/nifi/reporting/Severity.java b/src/main/java/org/apache/nifi/reporting/Severity.java new file mode 100644 index 0000000..8c2b27f --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/Severity.java
@@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.reporting; + +public enum Severity { + + INFO, + WARNING, + ERROR; +}
diff --git a/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java b/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java new file mode 100644 index 0000000..fe674be --- /dev/null +++ b/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java
@@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.reporting; + +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.components.ConfigVerificationResult; + +import java.util.List; + +/** + * <p> + * Any Reporting Task that implements this interface will be provided the opportunity to verify + * a given configuration of the Reporting Task. This allows the Reporting Task to provide meaningful feedback + * to users when configuring the dataflow. + * </p> + * + * <p> + * Generally speaking, verification differs from validation in that validation is expected to be very + * quick and run often. If a Reporting Task is not valid, it cannot be started. However, verification may be + * more expensive or time-consuming to complete. For example, validation may ensure that a username is + * provided for connecting to an external service but should not perform any sort of network connection + * in order to verify that the username is accurate. Verification, on the other hand, may create resources + * such as network connections, may be more expensive to complete, and may be run only when a user invokes + * the action (though verification may later occur at other stages, such as when starting a component). + * </p> + * + * <p> + * Verification is allowed to be run only when a Reporting Task is fully stopped. I.e., it has no active threads + * and currently has a state of STOPPED. Therefore, any initialization logic that may need to be performed + * before the Reporting Task is triggered may also be required for verification. However, the framework is not responsible + * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such + * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog)} itself. + * The {@link #verify(ConfigurationContext, ComponentLog)} method will only be called if the configuration is valid according to the + * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid). + * </p> + */ +public interface VerifiableReportingTask { + + /** + * Verifies that the configuration defined by the given ConfigurationContext is valid. + * + * @param context the Configuration Context that contains the necessary configuration + * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result + * in producing bulletins, which can be confusing. + * + * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed + */ + List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger); + +}
diff --git a/src/main/java/org/apache/nifi/scheduling/ExecutionNode.java b/src/main/java/org/apache/nifi/scheduling/ExecutionNode.java new file mode 100644 index 0000000..e41f703 --- /dev/null +++ b/src/main/java/org/apache/nifi/scheduling/ExecutionNode.java
@@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.scheduling; + +/** + * Defines the Nodes where a given Component will be scheduled to run. + */ +public enum ExecutionNode { + /** + * A Component will be scheduled to run on all nodes. + */ + ALL, + /** + * A Component will be scheduled to run on the primary node only. + */ + PRIMARY; +}
diff --git a/src/main/java/org/apache/nifi/scheduling/SchedulingStrategy.java b/src/main/java/org/apache/nifi/scheduling/SchedulingStrategy.java new file mode 100644 index 0000000..40375cc --- /dev/null +++ b/src/main/java/org/apache/nifi/scheduling/SchedulingStrategy.java
@@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.scheduling; + +/** + * Defines a Scheduling Strategy to use when scheduling Components (Ports, + * Funnels, Processors) to run + */ +public enum SchedulingStrategy { + + /** + * Components should be scheduled to run on a periodic interval that is + * user-defined with a user-defined number of concurrent tasks. All + * Components support Timer-Driven mode. + */ + TIMER_DRIVEN(1, "0 sec"), + /** + * Indicates that the component will be scheduled to run according to a + * Cron-style expression + */ + CRON_DRIVEN(1, "* * * * * ?"); + + private final int defaultConcurrentTasks; + private final String defaultSchedulingPeriod; + + SchedulingStrategy(final int defaultConcurrentTasks, final String defaultSchedulingPeriod) { + this.defaultConcurrentTasks = defaultConcurrentTasks; + this.defaultSchedulingPeriod = defaultSchedulingPeriod; + } + + public int getDefaultConcurrentTasks() { + return defaultConcurrentTasks; + } + + public String getDefaultSchedulingPeriod() { + return defaultSchedulingPeriod; + } +}
diff --git a/src/main/java/org/apache/nifi/search/SearchContext.java b/src/main/java/org/apache/nifi/search/SearchContext.java new file mode 100644 index 0000000..24b0c2a --- /dev/null +++ b/src/main/java/org/apache/nifi/search/SearchContext.java
@@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.search; + +import java.util.Map; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; + +public interface SearchContext { + + /** + * @return the search term + */ + String getSearchTerm(); + + /** + * @return the annotation data + */ + String getAnnotationData(); + + /** + * @param property to get value of + * @return a PropertyValue that encapsulates the value configured for the + * given PropertyDescriptor + */ + PropertyValue getProperty(PropertyDescriptor property); + + /** + * @return a Map of all configured Properties + */ + Map<PropertyDescriptor, String> getProperties(); +}
diff --git a/src/main/java/org/apache/nifi/search/SearchResult.java b/src/main/java/org/apache/nifi/search/SearchResult.java new file mode 100644 index 0000000..7bc32a5 --- /dev/null +++ b/src/main/java/org/apache/nifi/search/SearchResult.java
@@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.search; + +public class SearchResult { + + private final String label; + private final String match; + + private SearchResult(final Builder builder) { + this.label = builder.label; + this.match = builder.match; + } + + /** + * @return the label for this search result + */ + public String getLabel() { + return label; + } + + /** + * @return the matching string for this search result + */ + public String getMatch() { + return match; + } + + public static final class Builder { + + private String label; + private String match; + + /** + * Set the label for the search result. + * + * @param label to set + * @return the builder + */ + public Builder label(final String label) { + this.label = label; + return this; + } + + /** + * Set the matching string for the search result. + * + * @param match string + * @return the builder + */ + public Builder match(final String match) { + this.match = match; + return this; + } + + public SearchResult build() { + return new SearchResult(this); + } + + } +}
diff --git a/src/main/java/org/apache/nifi/search/Searchable.java b/src/main/java/org/apache/nifi/search/Searchable.java new file mode 100644 index 0000000..c1a0a34 --- /dev/null +++ b/src/main/java/org/apache/nifi/search/Searchable.java
@@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.search; + +import java.util.Collection; + +public interface Searchable { + + Collection<SearchResult> search(SearchContext context); +}
diff --git a/src/main/java/org/apache/nifi/time/DurationFormat.java b/src/main/java/org/apache/nifi/time/DurationFormat.java new file mode 100644 index 0000000..6de336a --- /dev/null +++ b/src/main/java/org/apache/nifi/time/DurationFormat.java
@@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.time; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DurationFormat { + private static final String UNION = "|"; + + + // for Time Durations + private static final String NANOS = String.join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds"); + private static final String MILLIS = String.join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds"); + private static final String SECS = String.join(UNION, "s", "sec", "secs", "second", "seconds"); + private static final String MINS = String.join(UNION, "m", "min", "mins", "minute", "minutes"); + private static final String HOURS = String.join(UNION, "h", "hr", "hrs", "hour", "hours"); + private static final String DAYS = String.join(UNION, "d", "day", "days"); + private static final String WEEKS = String.join(UNION, "w", "wk", "wks", "week", "weeks"); + + private static final String VALID_TIME_UNITS = String.join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS); + public static final String TIME_DURATION_REGEX = "([\\d.]+)\\s*(" + VALID_TIME_UNITS + ")"; + public static final Pattern TIME_DURATION_PATTERN = Pattern.compile(TIME_DURATION_REGEX); + private static final List<Long> TIME_UNIT_MULTIPLIERS = Arrays.asList(1000L, 1000L, 1000L, 60L, 60L, 24L); + + private DurationFormat() { + } + + /** + * Returns a time duration in the requested {@link TimeUnit} after parsing the {@code String} + * input. If the resulting value is a decimal (i.e. + * {@code 25 hours -> TimeUnit.DAYS = 1.04}), the value is rounded. + * Use {@link #getPreciseTimeDuration(String, TimeUnit)} if fractional values are desirable + * + * @param value the raw String input (i.e. "28 minutes") + * @param desiredUnit the requested output {@link TimeUnit} + * @return the whole number value of this duration in the requested units + * @see #getPreciseTimeDuration(String, TimeUnit) + */ + public static long getTimeDuration(final String value, final TimeUnit desiredUnit) { + return Math.round(getPreciseTimeDuration(value, desiredUnit)); + } + + /** + * Returns the parsed and converted input in the requested units. + * <p> + * If the value is {@code 0 <= x < 1} in the provided units, the units will first be converted to a smaller unit to get a value >= 1 (i.e. 0.5 seconds -> 500 milliseconds). + * This is because the underlying unit conversion cannot handle decimal values. + * <p> + * If the value is {@code x >= 1} but x is not a whole number, the units will first be converted to a smaller unit to attempt to get a whole number value (i.e. 1.5 seconds -> 1500 milliseconds). + * <p> + * If the value is {@code x < 1000} and the units are {@code TimeUnit.NANOSECONDS}, the result will be a whole number of nanoseconds, rounded (i.e. 123.4 ns -> 123 ns). + * <p> + * This method handles decimal values over {@code 1 ns}, but {@code < 1 ns} will return {@code 0} in any other unit. + * <p> + * Examples: + * <p> + * "10 seconds", {@code TimeUnit.MILLISECONDS} -> 10_000.0 + * "0.010 s", {@code TimeUnit.MILLISECONDS} -> 10.0 + * "0.010 s", {@code TimeUnit.SECONDS} -> 0.010 + * "0.010 ns", {@code TimeUnit.NANOSECONDS} -> 1 + * "0.010 ns", {@code TimeUnit.MICROSECONDS} -> 0 + * + * @param value the {@code String} input + * @param desiredUnit the desired output {@link TimeUnit} + * @return the parsed and converted amount (without a unit) + */ + public static double getPreciseTimeDuration(final String value, final TimeUnit desiredUnit) { + final Matcher matcher = TIME_DURATION_PATTERN.matcher(value.toLowerCase()); + if (!matcher.matches()) { + throw new IllegalArgumentException("Value '" + value + "' is not a valid time duration"); + } + + final String duration = matcher.group(1); + final String units = matcher.group(2); + + double durationVal = Double.parseDouble(duration); + TimeUnit specifiedTimeUnit; + + // The TimeUnit enum doesn't have a value for WEEKS, so handle this case independently + if (isWeek(units)) { + specifiedTimeUnit = TimeUnit.DAYS; + durationVal *= 7; + } else { + specifiedTimeUnit = determineTimeUnit(units); + } + + // The units are now guaranteed to be in DAYS or smaller + long durationLong; + if (durationVal == Math.rint(durationVal)) { + durationLong = Math.round(durationVal); + } else { + // Try reducing the size of the units to make the input a long + List<?> wholeResults = makeWholeNumberTime(durationVal, specifiedTimeUnit); + durationLong = (long) wholeResults.get(0); + specifiedTimeUnit = (TimeUnit) wholeResults.get(1); + } + + return desiredUnit.convert(durationLong, specifiedTimeUnit); + } + + /** + * Converts the provided time duration value to one that can be represented as a whole number. + * Returns a {@code List} containing the new value as a {@code long} at index 0 and the + * {@link TimeUnit} at index 1. If the incoming value is already whole, it is returned as is. + * If the incoming value cannot be made whole, a whole approximation is returned. For values + * {@code >= 1 TimeUnit.NANOSECONDS}, the value is rounded (i.e. 123.4 ns -> 123 ns). + * For values {@code < 1 TimeUnit.NANOSECONDS}, the constant [1L, {@code TimeUnit.NANOSECONDS}] is returned as the smallest measurable unit of time. + * <p> + * Examples: + * <p> + * 1, {@code TimeUnit.SECONDS} -> [1, {@code TimeUnit.SECONDS}] + * 1.1, {@code TimeUnit.SECONDS} -> [1100, {@code TimeUnit.MILLISECONDS}] + * 0.1, {@code TimeUnit.SECONDS} -> [100, {@code TimeUnit.MILLISECONDS}] + * 0.1, {@code TimeUnit.NANOSECONDS} -> [1, {@code TimeUnit.NANOSECONDS}] + * + * @param decimal the time duration as a decimal + * @param timeUnit the current time unit + * @return the time duration as a whole number ({@code long}) and the smaller time unit used + */ + static List<Object> makeWholeNumberTime(double decimal, TimeUnit timeUnit) { + // If the value is already a whole number, return it and the current time unit + if (decimal == Math.rint(decimal)) { + final long rounded = Math.round(decimal); + return Arrays.asList(new Object[]{rounded, timeUnit}); + } else if (TimeUnit.NANOSECONDS == timeUnit) { + // The time unit is as small as possible + if (decimal < 1.0) { + decimal = 1; + } else { + decimal = Math.rint(decimal); + } + return Arrays.asList(new Object[]{(long) decimal, timeUnit}); + } else { + // Determine the next time unit and the respective multiplier + TimeUnit smallerTimeUnit = getSmallerTimeUnit(timeUnit); + long multiplier = calculateMultiplier(timeUnit, smallerTimeUnit); + + // Recurse with the original number converted to the smaller unit + return makeWholeNumberTime(decimal * multiplier, smallerTimeUnit); + } + } + + /** + * Returns the numerical multiplier to convert a value from {@code originalTimeUnit} to + * {@code newTimeUnit} (i.e. for {@code TimeUnit.DAYS -> TimeUnit.MINUTES} would return + * 24 * 60 = 1440). If the original and new units are the same, returns 1. If the new unit + * is larger than the original (i.e. the result would be less than 1), throws an + * {@link IllegalArgumentException}. + * + * @param originalTimeUnit the source time unit + * @param newTimeUnit the destination time unit + * @return the numerical multiplier between the units + */ + static long calculateMultiplier(TimeUnit originalTimeUnit, TimeUnit newTimeUnit) { + if (originalTimeUnit == newTimeUnit) { + return 1; + } else if (originalTimeUnit.ordinal() < newTimeUnit.ordinal()) { + throw new IllegalArgumentException("The original time unit '" + originalTimeUnit + "' must be larger than the new time unit '" + newTimeUnit + "'"); + } else { + int originalOrd = originalTimeUnit.ordinal(); + int newOrd = newTimeUnit.ordinal(); + + List<Long> unitMultipliers = TIME_UNIT_MULTIPLIERS.subList(newOrd, originalOrd); + return unitMultipliers.stream().reduce(1L, (a, b) -> (long) a * b); + } + } + + /** + * Returns the next smallest {@link TimeUnit} (i.e. {@code TimeUnit.DAYS -> TimeUnit.HOURS}). + * If the parameter is {@code null} or {@code TimeUnit.NANOSECONDS}, an + * {@link IllegalArgumentException} is thrown because there is no valid smaller TimeUnit. + * + * @param originalUnit the TimeUnit + * @return the next smaller TimeUnit + */ + static TimeUnit getSmallerTimeUnit(TimeUnit originalUnit) { + if (originalUnit == null || TimeUnit.NANOSECONDS == originalUnit) { + throw new IllegalArgumentException("Cannot determine a smaller time unit than '" + originalUnit + "'"); + } else { + return TimeUnit.values()[originalUnit.ordinal() - 1]; + } + } + + /** + * Returns {@code true} if this raw unit {@code String} is parsed as representing "weeks", which does not have a value in the {@link TimeUnit} enum. + * + * @param rawUnit the String containing the desired unit + * @return true if the unit is "weeks"; false otherwise + */ + private static boolean isWeek(final String rawUnit) { + return switch (rawUnit) { + case "w", "wk", "wks", "week", "weeks" -> true; + default -> false; + }; + } + + /** + * Returns the {@link TimeUnit} enum that maps to the provided raw {@code String} input. The + * highest time unit is {@code TimeUnit.DAYS}. Any input that cannot be parsed will result in + * an {@link IllegalArgumentException}. + * + * @param rawUnit the String to parse + * @return the TimeUnit + */ + protected static TimeUnit determineTimeUnit(String rawUnit) { + return switch (rawUnit.toLowerCase()) { + case "ns", "nano", "nanos", "nanoseconds" -> TimeUnit.NANOSECONDS; + case "µs", "micro", "micros", "microseconds" -> TimeUnit.MICROSECONDS; + case "ms", "milli", "millis", "milliseconds" -> TimeUnit.MILLISECONDS; + case "s", "sec", "secs", "second", "seconds" -> TimeUnit.SECONDS; + case "m", "min", "mins", "minute", "minutes" -> TimeUnit.MINUTES; + case "h", "hr", "hrs", "hour", "hours" -> TimeUnit.HOURS; + case "d", "day", "days" -> TimeUnit.DAYS; + default -> throw new IllegalArgumentException("Could not parse '" + rawUnit + "' to TimeUnit"); + }; + } +}
diff --git a/src/test/java/org/apache/nifi/components/EnumDescribedValue.java b/src/test/java/org/apache/nifi/components/EnumDescribedValue.java new file mode 100644 index 0000000..ede70c9 --- /dev/null +++ b/src/test/java/org/apache/nifi/components/EnumDescribedValue.java
@@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +public enum EnumDescribedValue implements DescribedValue { + + GREEN { + @Override + public String getDisplayName() { + return "GreenDisplayName"; + } + + @Override + public String getDescription() { + return "GreenDescription"; + } + }, + + RED { + @Override + public String getDisplayName() { + return "RedDisplayName"; + } + + @Override + public String getDescription() { + return "RedDescription"; + } + }, + + BLUE { + @Override + public String getDisplayName() { + return "BlueDisplayName"; + } + + @Override + public String getDescription() { + return "BlueDescription"; + } + }; + + @Override + public String getValue() { + return name(); + } +}
diff --git a/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java b/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java new file mode 100644 index 0000000..a280c9c --- /dev/null +++ b/src/test/java/org/apache/nifi/components/EnumNotDescribedValue.java
@@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +public enum EnumNotDescribedValue { + GREEN, RED, BLUE; +}
diff --git a/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java b/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java new file mode 100644 index 0000000..c3288e8 --- /dev/null +++ b/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
@@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.components; + +import org.apache.nifi.components.PropertyDescriptor.Builder; +import org.apache.nifi.components.resource.ResourceCardinality; +import org.apache.nifi.components.resource.ResourceType; +import org.apache.nifi.expression.ExpressionLanguageScope; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; + +public class TestPropertyDescriptor { + + private static final String DEFAULT_VALUE = "Default Value"; + private static final String DEPENDENT_PROPERTY_NAME = "dependentProperty"; + + @Nested + class RegardingDefaultValue { + @Test + void supportsStringValues() { + final PropertyDescriptor descriptor = builder().defaultValue(DEFAULT_VALUE).build(); + + assertEquals(DEFAULT_VALUE, descriptor.getDefaultValue()); + } + + @Test + void supportsDescribedValuesValues() { + final PropertyDescriptor descriptor = builder().defaultValue(EnumDescribedValue.GREEN).build(); + + assertEquals(EnumDescribedValue.GREEN.getValue(), descriptor.getDefaultValue()); + } + + /** + * Regression test for issue NIFI-49, to ensure that if a Processor's Property's + * Default Value is not allowed, the Exception thrown should indicate what the default value is + */ + @Test + void throwsIllegalStateExceptionWhenDefaultValueNotInAllowableValues() { + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + builder().allowableValues("NOT DEFAULT", "OTHER NOT DEFAULT").defaultValue(DEFAULT_VALUE).build(); + }); + assertTrue(exception.getMessage().contains("[" + DEFAULT_VALUE + "]")); + } + + @Test + void canBeCleared() { + final PropertyDescriptor descriptorWithDefault = builder().defaultValue(DEFAULT_VALUE).build(); + final PropertyDescriptor resetDescriptor = builder(descriptorWithDefault).clearDefaultValue().build(); + + assertNull(resetDescriptor.getDefaultValue()); + } + } + + @Nested + class RegardingAllowableValues { + + private static final Comparator<AllowableValue> allowableValueComparator = Comparator.comparing(AllowableValue::getValue); + private final List<AllowableValue> expectedMinimalAllowableValues = + List.of(new AllowableValue("GREEN"), new AllowableValue("RED"), new AllowableValue("BLUE")); + private final List<AllowableValue> expectedAllowableValuesWithDescription = + Arrays.stream(EnumDescribedValue.values()).map(AllowableValue::fromDescribedValue).toList(); + + @Test + void supportsStringVarArgValues() { + final List<AllowableValue> expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues("GREEN", "RED", "BLUE").build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsStringSetValues() { + final List<AllowableValue> expected = sort(expectedMinimalAllowableValues); + + final PropertyDescriptor descriptor = builder().allowableValues(Set.of("GREEN", "RED", "BLUE")).build(); + // the iteration order of sets is not guaranteed by all implementations, thus we unify the order here + final List<AllowableValue> actual = sort(descriptor.getAllowableValues()); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsEnumArrayValues() { + final List<AllowableValue> expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumNotDescribedValue.values()).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void supportsDescribedValueEnumArrayValues() { + final List<AllowableValue> expected = expectedAllowableValuesWithDescription; + + final Enum[] enumArray = EnumDescribedValue.values(); + final PropertyDescriptor descriptor = builder().allowableValues(enumArray).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsEnumClassValues() { + final List<AllowableValue> expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumNotDescribedValue.class).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueEnumClassValues() { + final List<AllowableValue> expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumDescribedValue.class).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsEnumSetValues() { + final List<AllowableValue> expected = expectedMinimalAllowableValues; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumSet.allOf(EnumNotDescribedValue.class)).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueEnumSetValues() { + final List<AllowableValue> expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder().allowableValues(EnumSet.allOf(EnumDescribedValue.class)).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + @Test + void supportsDescribedValueVarArgValues() { + final List<AllowableValue> expected = expectedAllowableValuesWithDescription; + + final PropertyDescriptor descriptor = builder() + .allowableValues(EnumDescribedValue.GREEN, EnumDescribedValue.RED, EnumDescribedValue.BLUE).build(); + final List<AllowableValue> actual = descriptor.getAllowableValues(); + + assertEquals(expected, actual); // equals only compares getValue() + assertEquals(displayNamesOf(expected), displayNamesOf(actual)); + assertEquals(descriptionsOf(expected), descriptionsOf(actual)); + } + + private List<AllowableValue> sort(final List<AllowableValue> allowableValues) { + return allowableValues.stream().sorted(allowableValueComparator).toList(); + } + + private List<String> displayNamesOf(final List<AllowableValue> allowableValues) { + return allowableValues.stream().map(AllowableValue::getDisplayName).toList(); + } + + private List<String> descriptionsOf(final List<AllowableValue> allowableValues) { + return allowableValues.stream().map(AllowableValue::getDescription).toList(); + } + } + + @Test + void testDependsOnWithEnumValue() { + final PropertyDescriptor dependentPropertyDescriptor = new PropertyDescriptor.Builder() + .name(DEPENDENT_PROPERTY_NAME) + .build(); + + final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder() + .name("enumDependsOnDescriptor") + .dependsOn(dependentPropertyDescriptor, EnumDescribedValue.RED) + .build(); + + assertNotNull(propertyDescriptor); + + final Set<PropertyDependency> dependencies = propertyDescriptor.getDependencies(); + assertEquals(1, dependencies.size()); + final PropertyDependency dependency = dependencies.iterator().next(); + assertEquals(DEPENDENT_PROPERTY_NAME, dependency.getPropertyName()); + final Set<String> dependentValues = dependency.getDependentValues(); + assertEquals(1, dependentValues.size()); + final String dependentValue = dependentValues.iterator().next(); + assertEquals(EnumDescribedValue.RED.getValue(), dependentValue); + } + + @Test + void testExternalResourceIgnoredIfELWithAttributesPresent() { + final PropertyDescriptor descriptor = new PropertyDescriptor.Builder() + .name("dir") + .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE) + .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) + .required(false) + .build(); + + final ValidationContext validationContext = Mockito.mock(ValidationContext.class); + Mockito.when(validationContext.isExpressionLanguagePresent(anyString())).thenReturn(true); + Mockito.when(validationContext.isExpressionLanguageSupported(anyString())).thenReturn(true); + Mockito.when(validationContext.newPropertyValue(anyString())).thenAnswer(invocation -> { + final String inputArg = invocation.getArgument(0); + return inputArg.replace("${TestPropertyDescriptor.Var1}", "__my_var__").replaceAll("\\$\\{.*}", ""); + }); + + assertTrue(descriptor.validate("${TestPropertyDescriptor.Var1}", validationContext).isValid()); + } + + @Test + void testExternalResourceConsideredIfELVarRegistryPresent() { + final PropertyDescriptor descriptor = new PropertyDescriptor.Builder() + .name("dir") + .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY) + .expressionLanguageSupported(ExpressionLanguageScope.ENVIRONMENT) + .required(false) + .build(); + + final AtomicReference<String> variable = new AtomicReference<>("__my_var__"); + final ValidationContext validationContext = Mockito.mock(ValidationContext.class); + Mockito.when(validationContext.isExpressionLanguagePresent(anyString())).thenReturn(true); + Mockito.when(validationContext.isExpressionLanguageSupported(anyString())).thenReturn(true); + Mockito.when(validationContext.newPropertyValue(anyString())).thenAnswer(invocation -> { + final String inputArg = invocation.getArgument(0); + final String evaluatedValue = inputArg.replace("${TestPropertyDescriptor.Var1}", variable.get().replaceAll("\\$\\{.*}", "")); + + final PropertyValue propertyValue = Mockito.mock(PropertyValue.class); + Mockito.when(propertyValue.getValue()).thenReturn(evaluatedValue); + Mockito.when(propertyValue.evaluateAttributeExpressions()).thenReturn(propertyValue); + return propertyValue; + }); + + // Should not be valid because Expression Language scope is ENVIRONMENT, so the ${TestPropertyDescriptor.Var1} will be replaced with + // __my_var__, and __my_var__ does not exist. + assertFalse(descriptor.validate("${TestPropertyDescriptor.Var1}", validationContext).isValid()); + + // Will now be valid because variable changed to 'target', which does exist. + variable.set("target"); + assertTrue(descriptor.validate("${TestPropertyDescriptor.Var1}", validationContext).isValid()); + + // Consider if Expression Language is not supported. + Mockito.when(validationContext.isExpressionLanguageSupported(anyString())).thenReturn(false); + final PropertyDescriptor withElNotAllowed = new PropertyDescriptor.Builder() + .fromPropertyDescriptor(descriptor) + .expressionLanguageSupported(ExpressionLanguageScope.NONE) + .build(); + + // Expression will not be evaluated, so the directory being looked at will literally be ${TestPropertyDescriptor.Var1} + assertFalse(withElNotAllowed.validate("${TestPropertyDescriptor.Var1}", validationContext).isValid()); + + // Test the literal value 'target' + assertTrue(withElNotAllowed.validate("target", validationContext).isValid()); + } + + @Test + void testClearingValues() { + final PropertyDescriptor dep1 = new PropertyDescriptor.Builder() + .name("dep1") + .allowableValues("delVal1") + .build(); + + final PropertyDescriptor pd1 = new PropertyDescriptor.Builder() + .name("test") + .addValidator(Validator.VALID) + .allowableValues("val1") + .dependsOn(dep1, "depVal1") + .build(); + + final PropertyDescriptor pd2 = new PropertyDescriptor.Builder() + .fromPropertyDescriptor(pd1) + .clearValidators() + .clearAllowableValues() + .clearDependsOn() + .build(); + + assertEquals("test", pd1.getName()); + assertFalse(pd1.getValidators().isEmpty()); + assertFalse(pd1.getDependencies().isEmpty()); + assertNotNull(pd1.getAllowableValues()); + + assertEquals("test", pd2.getName()); + assertTrue(pd2.getValidators().isEmpty()); + assertTrue(pd2.getDependencies().isEmpty()); + assertNull(pd2.getAllowableValues()); + } + + private Builder builder() { + return new PropertyDescriptor.Builder().name("propertyName"); + } + + private Builder builder(final PropertyDescriptor propertyDescriptor) { + return new PropertyDescriptor.Builder().fromPropertyDescriptor(propertyDescriptor); + } +}
diff --git a/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferenceFactory.java b/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferenceFactory.java new file mode 100644 index 0000000..5a297d9 --- /dev/null +++ b/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferenceFactory.java
@@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestStandardResourceReferenceFactory { + + private final StandardResourceReferenceFactory subject = new StandardResourceReferenceFactory(); + + @Test + public void testCreateResourceReferences() { + String value = "/dir1/test1.jar,/dir2/test2.jar"; + ResourceDefinition resourceDefinition = createResourceDefinition(); + + ResourceReferences resourceReferences = subject.createResourceReferences(value, resourceDefinition); + + assertNotNull(resourceReferences); + + List<ResourceReference> resourceReferencesList = resourceReferences.asList(); + assertNotNull(resourceReferencesList); + assertEquals(2, resourceReferencesList.size()); + + assertResourceReference(resourceReferencesList.get(0), "/dir1/test1.jar"); + assertResourceReference(resourceReferencesList.get(1), "/dir2/test2.jar"); + } + + @Test + public void testCreateResourceReferencesWhenValueIsNull() { + String value = null; + ResourceDefinition resourceDefinition = createResourceDefinition(); + + ResourceReferences resourceReferences = subject.createResourceReferences(value, resourceDefinition); + + assertEmptyResourceReferences(resourceReferences); + } + + @Test + public void testCreateResourceReferencesWhenValueIsEmpty() { + String value = ""; + ResourceDefinition resourceDefinition = createResourceDefinition(); + + ResourceReferences resourceReferences = subject.createResourceReferences(value, resourceDefinition); + + assertEmptyResourceReferences(resourceReferences); + } + @Test + public void testCreateResourceReferencesWhenResourceDefinitionIsNull() { + String value = "/dir1/test1.jar"; + ResourceDefinition resourceDefinition = null; + + ResourceReferences resourceReferences = subject.createResourceReferences(value, resourceDefinition); + + assertEmptyResourceReferences(resourceReferences); + } + + private StandardResourceDefinition createResourceDefinition() { + return new StandardResourceDefinition(ResourceCardinality.SINGLE, Collections.singleton(ResourceType.FILE)); + } + + private void assertResourceReference(ResourceReference resourceReference, String location) { + assertEquals(new File(location).getAbsolutePath(), resourceReference.getLocation()); + assertEquals(ResourceType.FILE, resourceReference.getResourceType()); + } + + private void assertEmptyResourceReferences(ResourceReferences resourceReferences) { + assertNotNull(resourceReferences); + assertNotNull(resourceReferences.asList()); + assertTrue(resourceReferences.asList().isEmpty()); + } +}
diff --git a/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferences.java b/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferences.java new file mode 100644 index 0000000..ea2a0a5 --- /dev/null +++ b/src/test/java/org/apache/nifi/components/resource/TestStandardResourceReferences.java
@@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.components.resource; + +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestStandardResourceReferences { + + @Test + public void testAsURLs() { + final ResourceReferences references = new StandardResourceReferences(Arrays.asList( + new FileResourceReference(new File("target/1.txt")), + new FileResourceReference(new File("target/2.txt")) + )); + + final List<URL> urls = references.asURLs(); + assertEquals(2, urls.size()); + for (final URL url : urls) { + final String filename = url.getFile(); + assertTrue(filename.endsWith(".txt")); + + final File parentFile = new File(filename).getParentFile(); + assertEquals("target", parentFile.getName()); + } + } + + @Test + public void testFlattenRecursively() throws IOException { + // Create directory structure: + // target/dirs/ + // target/dirs/0 + // target/dirs/0/0 + // target/dirs/0/0/0.txt + // target/dirs/0/0/1.txt + // ... + // target/dirs/2/2/2.txt + final int numDirs = 3; + final int numSubDirs = 3; + final int numFiles = 3; + + final File baseDir = new File("target/dirs"); + for (int i = 0; i < numDirs; i++) { + final File dir = new File(baseDir, String.valueOf(i)); + dir.mkdirs(); + + for (int j = 0; j < numSubDirs; j++) { + final File subdir = new File(dir, String.valueOf(j)); + subdir.mkdirs(); + + for (int k = 0; k < numFiles; k++) { + final File file = new File(subdir, k + ".txt"); + + try (final OutputStream fos = new FileOutputStream(file)) { + fos.write((k + ".txt").getBytes(StandardCharsets.UTF_8)); + } + } + } + } + + final ResourceReferences references = new StandardResourceReferences(Collections.singletonList(new FileResourceReference(baseDir))); + assertEquals(1, references.getCount()); + assertEquals(ResourceType.DIRECTORY, references.asList().get(0).getResourceType()); + + final ResourceReferences flattened = references.flattenRecursively(); + assertEquals(numDirs * numSubDirs * numFiles, flattened.getCount()); + + final List<ResourceReference> flattenedReferences = flattened.asList(); + assertEquals(numDirs * numSubDirs * numFiles, flattenedReferences.size()); + + // Ensure that each file that was flattened has a unique filename and the file exists. + final Set<String> filenames = new HashSet<>(); + for (final ResourceReference reference : flattenedReferences) { + assertEquals(ResourceType.FILE, reference.getResourceType()); + + final String filename = reference.getLocation(); + assertTrue(filename.endsWith(".txt")); + + filenames.add(filename); + assertTrue(new File(filename).exists()); + } + + assertEquals(numDirs * numSubDirs * numFiles, filenames.size()); + } + + @Test + public void testFlatten() throws IOException { + // Create directory structure: + // target/dir + // target/dir/0 + // target/dir/0/0.txt + // target/dir/0/1.txt + // ... + // target/dir/0.txt + // target/dir/1.txt + // ... + final int numFiles = 3; + + final File baseDir = new File("target/dir"); + baseDir.mkdirs(); + + for (int i = 0; i < numFiles; i++) { + final File file = new File(baseDir, i + ".txt"); + + try (final OutputStream fos = new FileOutputStream(file)) { + fos.write((i + ".txt").getBytes(StandardCharsets.UTF_8)); + } + } + + final ResourceReferences references = new StandardResourceReferences(Collections.singletonList(new FileResourceReference(baseDir))); + assertEquals(1, references.getCount()); + assertEquals(ResourceType.DIRECTORY, references.asList().get(0).getResourceType()); + + final ResourceReferences flattened = references.flatten(); + assertEquals(numFiles, flattened.getCount()); + + final List<ResourceReference> flattenedReferences = flattened.asList(); + assertEquals(numFiles, flattenedReferences.size()); + + // Ensure that each file that was flattened has a unique filename and the file exists. + final Set<String> filenames = new HashSet<>(); + for (final ResourceReference reference : flattenedReferences) { + assertEquals(ResourceType.FILE, reference.getResourceType()); + + final String filename = reference.getLocation(); + assertTrue(filename.endsWith(".txt")); + + filenames.add(filename); + assertTrue(new File(filename).exists()); + } + + assertEquals(numFiles, filenames.size()); + } +}
diff --git a/src/test/java/org/apache/nifi/parameter/TestParameter.java b/src/test/java/org/apache/nifi/parameter/TestParameter.java new file mode 100644 index 0000000..cc366ff --- /dev/null +++ b/src/test/java/org/apache/nifi/parameter/TestParameter.java
@@ -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. + */ + +package org.apache.nifi.parameter; + +import org.apache.nifi.asset.Asset; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestParameter { + + @Test + public void testCreateParameterWithValue() { + final Parameter parameter = new Parameter.Builder() + .name("A") + .value("value") + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals("value", parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertTrue(parameter.getReferencedAssets().isEmpty()); + } + + @Test + public void testCreateParameterWithSingleReference() { + final File file = new File("file"); + final Asset asset = new MockAsset("id", "parmContext", "name", file, "asset-digest"); + + final Parameter parameter = new Parameter.Builder() + .name("A") + .referencedAssets(List.of(asset)) + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals(file.getAbsolutePath(), parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(1, parameter.getReferencedAssets().size()); + assertEquals(asset, parameter.getReferencedAssets().getFirst()); + } + + @Test + public void testCreateParameterWithMultipleReferences() { + final File file1 = new File("file1"); + final File file2 = new File("file2"); + final File file3 = new File("file3"); + final Asset asset1 = new MockAsset("id1", "parmContext", "name1", file1, "asset-digest"); + final Asset asset2 = new MockAsset("id2", "parmContext", "name2", file2, "asset-digest"); + final Asset asset3 = new MockAsset("id3", "parmContext", "name3", file3, "asset-digest"); + + final Parameter parameter = new Parameter.Builder() + .name("A") + .referencedAssets(List.of(asset1, asset2, asset3)) + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals(file1.getAbsolutePath() + "," + file2.getAbsolutePath() + "," + file3.getAbsolutePath(), parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(3, parameter.getReferencedAssets().size()); + } + + @Test + public void testCreateParameterWithValueThenAsset() { + final File file = new File("file"); + final Asset asset = new MockAsset("id", "parmContext", "name", file, "asset-digest"); + + final Parameter parameter = new Parameter.Builder() + .name("A") + .value("value") + .referencedAssets(List.of(asset)) + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals(file.getAbsolutePath(), parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(1, parameter.getReferencedAssets().size()); + assertEquals(asset, parameter.getReferencedAssets().getFirst()); + } + + @Test + public void testCreateParameterAssetThenValue() { + final File file = new File("file"); + final Asset asset = new MockAsset("id", "parmContext", "name", file, "asset-digest"); + + final Parameter parameter = new Parameter.Builder() + .name("A") + .referencedAssets(List.of(asset)) + .value("value") + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals("value", parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(0, parameter.getReferencedAssets().size()); + } + + @Test + public void testCreateParameterFromOtherThenOverrideWithAsset() { + final File file = new File("file"); + final Asset asset = new MockAsset("id", "parmContext", "name", file, "asset-digest"); + + final Parameter original = new Parameter.Builder() + .name("A") + .value("value") + .build(); + + final Parameter parameter = new Parameter.Builder() + .fromParameter(original) + .referencedAssets(List.of(asset)) + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(1, parameter.getReferencedAssets().size()); + assertEquals(asset, parameter.getReferencedAssets().getFirst()); + } + + @Test + public void testCreateParameterFromOtherThenOverrideWithValue() { + final File file = new File("file"); + final Asset asset = new MockAsset("id", "parmContext", "name", file, "asset-digest"); + + final Parameter original = new Parameter.Builder() + .name("A") + .referencedAssets(List.of(asset)) + .build(); + + final Parameter parameter = new Parameter.Builder() + .fromParameter(original) + .value("value") + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals("value", parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(0, parameter.getReferencedAssets().size()); + } + + @Test + public void testCreateParameterFromOtherThenOverrideWithDifferentValue() { + final Parameter original = new Parameter.Builder() + .name("A") + .value("value 1") + .build(); + + final Parameter parameter = new Parameter.Builder() + .fromParameter(original) + .value("value 2") + .build(); + + assertEquals("A", parameter.getDescriptor().getName()); + assertEquals("value 2", parameter.getValue()); + assertNotNull(parameter.getReferencedAssets()); + assertEquals(0, parameter.getReferencedAssets().size()); + } + + private static class MockAsset implements Asset { + private final String identifier; + private final String parameterContextIdentifier; + private final String name; + private final File file; + private final String digest; + + public MockAsset(final String identifier, final String parameterContextIdentifier, final String name, final File file, final String digest) { + this.identifier = identifier; + this.parameterContextIdentifier = parameterContextIdentifier; + this.name = name; + this.file = file; + this.digest = digest; + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public String getParameterContextIdentifier() { + return parameterContextIdentifier; + } + + @Override + public String getName() { + return name; + } + + @Override + public File getFile() { + return file; + } + + @Override + public Optional<String> getDigest() { + return Optional.ofNullable(digest); + } + } +}
diff --git a/src/test/java/org/apache/nifi/processor/TestDataUnit.java b/src/test/java/org/apache/nifi/processor/TestDataUnit.java new file mode 100644 index 0000000..6df51f7 --- /dev/null +++ b/src/test/java/org/apache/nifi/processor/TestDataUnit.java
@@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processor; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestDataUnit { + + @Test + public void testParseWithIntegerValue() { + assertEquals(300L, DataUnit.parseDataSize("300 B", DataUnit.B).longValue()); + assertEquals(300L * 1024L, DataUnit.parseDataSize("300 KB", DataUnit.B).longValue()); + assertEquals(300L * 1024L * 1024L, DataUnit.parseDataSize("300 MB", DataUnit.B).longValue()); + assertEquals(300L * 1024L * 1024L * 1024L, DataUnit.parseDataSize("300 GB", DataUnit.B).longValue()); + } + + @Test + public void testParseWithDecimalValue() { + assertEquals(300L, DataUnit.parseDataSize("300 B", DataUnit.B).longValue()); + assertEquals((long) (3.22D * 1024D), DataUnit.parseDataSize("3.22 KB", DataUnit.B).longValue()); + assertEquals((long) (3.22D * 1024D * 1024D), DataUnit.parseDataSize("3.22 MB", DataUnit.B).longValue()); + assertEquals((long) (3.22D * 1024D * 1024D * 1024D), DataUnit.parseDataSize("3.22 GB", DataUnit.B).longValue()); + } + +}
diff --git a/src/test/java/org/apache/nifi/time/TestDurationFormat.java b/src/test/java/org/apache/nifi/time/TestDurationFormat.java new file mode 100644 index 0000000..3259de7 --- /dev/null +++ b/src/test/java/org/apache/nifi/time/TestDurationFormat.java
@@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.time; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestDurationFormat { + + @ParameterizedTest + @MethodSource("getParse") + public void testParse(String value, TimeUnit desiredUnit, Long expected) { + assertEquals(expected, DurationFormat.getTimeDuration(value, desiredUnit)); + } + + private static Stream<Arguments> getParse() { + return Stream.of(Arguments.of("3000 ms", TimeUnit.SECONDS, 3L), + Arguments.of("3000 s", TimeUnit.SECONDS, 3000L), + Arguments.of("999 millis", TimeUnit.SECONDS, 0L), + Arguments.of("4 days", TimeUnit.NANOSECONDS, 4L * 24L * 60L * 60L * 1000000000L), + Arguments.of("1 DAY", TimeUnit.HOURS, 24L), + Arguments.of("1 hr", TimeUnit.MINUTES, 60L), + Arguments.of("1 Hrs", TimeUnit.MINUTES, 60L)); + } + + @ParameterizedTest + @ValueSource(strings = {"1 week", "1 wk", "1 w", "1 wks", "1 weeks"}) + public void testGetTimeDurationShouldConvertWeeks(String week) { + assertEquals(7L, DurationFormat.getTimeDuration(week, TimeUnit.DAYS)); + } + + + @ParameterizedTest + @ValueSource(strings = {"-1 week", "-1 wk", "-1 w", "-1 weeks", "- 1 week"}) + public void testGetTimeDurationShouldHandleNegativeWeeks(String week) { + IllegalArgumentException iae = + assertThrows(IllegalArgumentException.class, () -> DurationFormat.getTimeDuration(week, TimeUnit.DAYS)); + assertTrue(iae.getMessage().contains("Value '" + week + "' is not a valid time duration")); + } + + + @ParameterizedTest + @ValueSource(strings = {"1 work", "1 wek", "1 k"}) + public void testGetTimeDurationShouldHandleInvalidAbbreviations(String week) { + IllegalArgumentException iae = + assertThrows(IllegalArgumentException.class, () -> DurationFormat.getTimeDuration(week, TimeUnit.DAYS)); + assertTrue(iae.getMessage().contains("Value '" + week + "' is not a valid time duration")); + } + + @ParameterizedTest + @ValueSource(strings = {"1week", "1wk", "1w", "1wks", "1weeks"}) + public void testGetTimeDurationShouldHandleNoSpaceInInput(String week) { + assertEquals(7L, DurationFormat.getTimeDuration(week, TimeUnit.DAYS)); + } + + + @ParameterizedTest + @ValueSource(strings = {"10 ms", "10 millis", "10 milliseconds"}) + public void testGetTimeDurationWithWholeNumbers(String whole) { + assertEquals(10L, DurationFormat.getTimeDuration(whole, TimeUnit.MILLISECONDS)); + } + + + @ParameterizedTest + @ValueSource(strings = {"0.010 s", "0.010 seconds"}) + public void testGetTimeDurationWithDecimalNumbers(String decimal) { + assertEquals(10L, DurationFormat.getTimeDuration(decimal, TimeUnit.MILLISECONDS)); + } + + @ParameterizedTest + @MethodSource("getOneWeekInOtherUnits") + public void testGetPreciseTimeDurationShouldHandleWeeks(TimeUnit timeUnit, long expected) { + assertEquals(expected, DurationFormat.getPreciseTimeDuration("1 week", timeUnit)); + } + + private static Stream<Arguments> getOneWeekInOtherUnits() { + return Stream.of(Arguments.of(TimeUnit.DAYS, 7L), + Arguments.of(TimeUnit.HOURS, 7 * 24L), + Arguments.of(TimeUnit.MINUTES, 7 * 24 * 60L), + Arguments.of(TimeUnit.SECONDS, 7 * 24 * 60 * 60L), + Arguments.of(TimeUnit.MILLISECONDS, 7 * 24 * 60 * 60 * 1000L), + Arguments.of(TimeUnit.MICROSECONDS, 7 * 24 * 60 * 60 * 1000 * 1000L), + Arguments.of(TimeUnit.NANOSECONDS, 7 * 24 * 60 * 60 * (1000 * 1000 * 1000L)) + ); + } + + @ParameterizedTest + @MethodSource("getOneAndAHalfWeeksInOtherUnits") + public void testGetPreciseTimeDurationShouldHandleDecimalWeeks(TimeUnit timeUnit, double expected) { + assertEquals(expected, DurationFormat.getPreciseTimeDuration("1.5 week", timeUnit)); + } + + private static Stream<Arguments> getOneAndAHalfWeeksInOtherUnits() { + final double oneAndAHalf = 1.5; + return Stream.of(Arguments.of(TimeUnit.HOURS, 7 * 24 * oneAndAHalf), + Arguments.of(TimeUnit.MINUTES, 7 * 24 * 60 * oneAndAHalf), + Arguments.of(TimeUnit.SECONDS, 7 * 24 * 60 * 60 * oneAndAHalf), + Arguments.of(TimeUnit.MILLISECONDS, 7 * 24 * 60 * 60 * 1000 * oneAndAHalf), + Arguments.of(TimeUnit.MICROSECONDS, 7 * 24 * 60 * 60 * (1000 * 1000L) * oneAndAHalf), + Arguments.of(TimeUnit.NANOSECONDS, 7 * 24 * 60 * 60 * (1000 * 1000 * 1000L) * oneAndAHalf)); + } + + @ParameterizedTest + @ValueSource(strings = {"10 ms", "10 millis", "10 milliseconds"}) + public void testGetPreciseTimeDurationWithWholeNumbers(String whole) { + assertEquals(10.0, DurationFormat.getPreciseTimeDuration(whole, TimeUnit.MILLISECONDS)); + } + + @ParameterizedTest + @ValueSource(strings = {"0.010 s", "0.010 seconds"}) + public void testGetPreciseTimeDurationWithDecimalNumbers(String decimal) { + assertEquals(10.0, DurationFormat.getPreciseTimeDuration(decimal, TimeUnit.MILLISECONDS)); + } + + @ParameterizedTest + @MethodSource("getTinyDecimalValues") + public void testGetPreciseTimeDurationShouldHandleSmallDecimalValues(String originalValue, TimeUnit desiredUnit, double expectedValue) { + assertEquals(expectedValue, DurationFormat.getPreciseTimeDuration(originalValue, desiredUnit)); + } + + private static Stream<Arguments> getTinyDecimalValues() { + return Stream.of(Arguments.of("123.4 " + TimeUnit.NANOSECONDS.name(), TimeUnit.NANOSECONDS, 123.0), + Arguments.of("0.9 " + TimeUnit.NANOSECONDS.name(), TimeUnit.NANOSECONDS, 1.0), + Arguments.of("0.9 " + TimeUnit.NANOSECONDS.name(), TimeUnit.MILLISECONDS, 0.0), + Arguments.of("123.4 " + TimeUnit.MILLISECONDS.name(), TimeUnit.NANOSECONDS, 123_400_000.0)); + } + + @ParameterizedTest + @EnumSource(TimeUnit.class) + public void testMakeWholeNumberTimeShouldHandleWholeNumbers(TimeUnit timeUnit) { + assertEquals(Arrays.asList(10L, timeUnit), DurationFormat.makeWholeNumberTime(10.0, timeUnit)); + } + + @ParameterizedTest + @MethodSource("getNanoSecondsForMakeWholeNumber") + public void testMakeWholeNumberTimeShouldHandleNanoseconds(double original, long expected) { + assertEquals(Arrays.asList(expected, TimeUnit.NANOSECONDS), + DurationFormat.makeWholeNumberTime(original, TimeUnit.NANOSECONDS)); + } + + private static Stream<Arguments> getNanoSecondsForMakeWholeNumber() { + return Stream.of(Arguments.of(1100.0, 1100L), + Arguments.of(2.1, 2L), + Arguments.of(1.0, 1L), + Arguments.of(0.1, 1L)); + } + + @ParameterizedTest + @NullSource + public void testGetSmallerTimeUnitWithNull(TimeUnit timeUnit) { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, + () -> DurationFormat.getSmallerTimeUnit(timeUnit)); + + assertEquals("Cannot determine a smaller time unit than 'null'", iae.getMessage()); + } + + @Test + public void testGetSmallerTimeUnitWithNanoseconds() { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, + () -> DurationFormat.getSmallerTimeUnit(TimeUnit.NANOSECONDS)); + + assertEquals("Cannot determine a smaller time unit than 'NANOSECONDS'", iae.getMessage()); + } + + @ParameterizedTest + @MethodSource("getSmallerUnits") + public void testShouldGetSmallerTimeUnit(TimeUnit original, TimeUnit expected) { + assertEquals(expected, DurationFormat.getSmallerTimeUnit(original)); + } + + private static Stream<Arguments> getSmallerUnits() { + return Stream.of(Arguments.of(TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS), + Arguments.of(TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS), + Arguments.of(TimeUnit.SECONDS, TimeUnit.MILLISECONDS), + Arguments.of(TimeUnit.MINUTES, TimeUnit.SECONDS), + Arguments.of(TimeUnit.HOURS, TimeUnit.MINUTES), + Arguments.of(TimeUnit.DAYS, TimeUnit.HOURS)); + } + + @ParameterizedTest + @MethodSource("getMultipliers") + void testShouldCalculateMultiplier(TimeUnit original, TimeUnit newTimeUnit, long expected) { + assertEquals(expected, DurationFormat.calculateMultiplier(original, newTimeUnit)); + } + + private static Stream<Arguments> getMultipliers() { + return Stream.of(Arguments.of(TimeUnit.DAYS, TimeUnit.NANOSECONDS, 24 * 60 * 60 * 1_000_000_000L), + Arguments.of(TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS, 1_000L), + Arguments.of(TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS, 1_000_000L), + Arguments.of(TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS, 1_000L), + Arguments.of(TimeUnit.DAYS, TimeUnit.HOURS, 24L), + Arguments.of(TimeUnit.DAYS, TimeUnit.SECONDS, 24 * 60 * 60L)); + } + + @ParameterizedTest + @MethodSource("getIncorrectUnits") + public void testCalculateMultiplierShouldHandleIncorrectUnits(TimeUnit original, TimeUnit newTimeUnit) { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, + () -> DurationFormat.calculateMultiplier(original, newTimeUnit)); + assertTrue(iae.getMessage().matches("The original time unit '.*' must be larger than the new time unit '.*'")); + } + + private static Stream<Arguments> getIncorrectUnits() { + return Stream.of(Arguments.of(TimeUnit.NANOSECONDS, TimeUnit.DAYS), + Arguments.of(TimeUnit.NANOSECONDS, TimeUnit.MICROSECONDS), + Arguments.of(TimeUnit.HOURS, TimeUnit.DAYS)); + } + + + @ParameterizedTest + @MethodSource("getDecimalsForMakeWholeNumber") + public void testMakeWholeNumberTimeShouldHandleDecimals(double decimal, TimeUnit timeUnit) { + assertEquals(Arrays.asList(10L, TimeUnit.NANOSECONDS), DurationFormat.makeWholeNumberTime(decimal, timeUnit)); + } + + @ParameterizedTest + @MethodSource("getDecimalsForMetricConversions") + public void testMakeWholeNumberTimeShouldHandleMetricConversions(double originalValue, TimeUnit originalUnits, long expectedValue, TimeUnit expectedUnits) { + assertEquals(Arrays.asList(expectedValue, expectedUnits), + DurationFormat.makeWholeNumberTime(originalValue, originalUnits)); + } + + private static Stream<Arguments> getDecimalsForMetricConversions() { + return Stream.of(Arguments.of(123.4, TimeUnit.SECONDS, 123_400L, TimeUnit.MILLISECONDS), + Arguments.of(1.000_345, TimeUnit.SECONDS, 1_000_345L, TimeUnit.MICROSECONDS), + Arguments.of(0.75, TimeUnit.MILLISECONDS, 750L, TimeUnit.MICROSECONDS), + Arguments.of(123.4, TimeUnit.NANOSECONDS, 123L, TimeUnit.NANOSECONDS), + Arguments.of(0.123, TimeUnit.NANOSECONDS, 1L, TimeUnit.NANOSECONDS)); + } + + @ParameterizedTest + @MethodSource("getDecimalsForNonMetricConversions") + public void testMakeWholeNumberTimeShouldHandleNonMetricConversions(double originalValue, TimeUnit originalUnits, long expectedValue, TimeUnit expectedUnits) { + assertEquals(Arrays.asList(expectedValue, expectedUnits), + DurationFormat.makeWholeNumberTime(originalValue, originalUnits)); + } + + private static Stream<Arguments> getDecimalsForNonMetricConversions() { + return Stream.of(Arguments.of(1.5, TimeUnit.DAYS, 36L, TimeUnit.HOURS), + Arguments.of(1.5, TimeUnit.HOURS, 90L, TimeUnit.MINUTES), + Arguments.of(.75, TimeUnit.HOURS, 45L, TimeUnit.MINUTES)); + } + + private static Stream<Arguments> getDecimalsForMakeWholeNumber() { + return Stream.of(Arguments.of(0.000_000_010, TimeUnit.SECONDS), + Arguments.of(0.000_010, TimeUnit.MILLISECONDS), + Arguments.of(0.010, TimeUnit.MICROSECONDS) + ); + } +}
diff --git a/src/test/java/org/apache/nifi/util/TestURLValidator.java b/src/test/java/org/apache/nifi/util/TestURLValidator.java new file mode 100644 index 0000000..6bbac56 --- /dev/null +++ b/src/test/java/org/apache/nifi/util/TestURLValidator.java
@@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.util; + +import org.apache.nifi.processor.util.URLValidator; +import org.junit.jupiter.api.Test; + +import java.net.MalformedURLException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestURLValidator { + + @Test + void testValidUri() { + final String url = "https://en.wikipedia.org/w/api.php?action=query&list=recentchanges&format=json&rcprop=user|comment|parsedcomment|timestamp|title|sizes|tags"; + assertDoesNotThrow(() -> URLValidator.createURL(url)); + } + + @Test + void testInvalidUri() { + final String uri = "http:// _"; + assertThrows(MalformedURLException.class, () -> URLValidator.createURL(uri)); + } +}