NIFI-6: Rebase from develop to include renaming of directory structure

This commit is contained in:
Mark Payne 2015-01-22 10:59:36 -05:00
commit 716e03b502
2205 changed files with 1241 additions and 13281 deletions

View File

@ -4,35 +4,15 @@ Apache NiFi is a dataflow system based on the concepts of flow-based programming
## Table of Contents
- [Features](#features)
- [Getting Started](#getting-started)
- [Getting Help](#getting-help)
- [Requirements](#requirements)
- [License](#license)
- [Disclaimer](#disclaimer)
## Features
Apache NiFi supports powerful and scalable directed graphs of data routing, transformation, and system mediation logic. Some of the high-level capabilities and objectives of Apache NiFi include:
- Web-based user interface for seamless experience between design, control, feedback, and monitoring of data flows
- Highly configurable along several dimensions of quality of service such as loss tolerant versus guaranteed delivery, low latency versus high throughput, and priority based queuing
- Fine-grained data provenance for all data received, forked, joined, cloned, modified, sent, and ultimately dropped as data reaches its configured end-state
- Component-based extension model along well defined interfaces enabling rapid development and effective testing
## Getting Started
Execute <nifi install dir>/bin/nifi.sh start
## Getting Help
If you have questions, you can reach out to our mailing list: dev@nifi.incubator.apache.org
([archive](http://mail-archives.apache.org/mod_mbox/incubator-nifi-dev)).
We're also often available in IRC: #nifi on
[irc.freenode.net](http://webchat.freenode.net/?channels=#nifi).
## Requirements
* JDK 1.7 or higher
- Build the nifi-nar-maven-plugin. Change directory to 'nifi-nar-maven-plugin' and
follow the directions found there.
- Build nifi. Change directory to 'nifi' and follow the directions found there.
## License
@ -64,32 +44,3 @@ While incubation status is not necessarily a reflection of the completeness
or stability of the code, it does indicate that the project has yet to be
fully endorsed by the ASF.
## Export Control
This distribution includes cryptographic software. The country in which you
currently reside may have restrictions on the import, possession, use, and/or
re-export to another country, of encryption software. BEFORE using any
encryption software, please check your country's laws, regulations and
policies concerning the import, possession, or use, and re-export of encryption
software, to see if this is permitted. See <http://www.wassenaar.org/> for more
information.
The U.S. Government Department of Commerce, Bureau of Industry and Security
(BIS), has classified this software as Export Commodity Control Number (ECCN)
5D002.C.1, which includes information security software using or performing
cryptographic functions with asymmetric algorithms. The form and manner of this
Apache Software Foundation distribution makes it eligible for export under the
License Exception ENC Technology Software Unrestricted (TSU) exception (see the
BIS Export Administration Regulations, Section 740.13) for both object code and
source code.
The following provides more details on the included cryptographic software:
Apache NiFi uses BouncyCastle, Jasypt, JCraft Inc., and the built-in
java cryptography libraries for SSL, SSH, and the protection
of sensitive configuration parameters. See
http://bouncycastle.org/about.html
http://www.jasypt.org/faq.html
http://jcraft.com/c-info.html
http://www.oracle.com/us/products/export/export-regulations-345813.html
for more details on each of these libraries cryptography features.

View File

@ -1,75 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>maven-plugins</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>nar-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Apache NiFi NAR Plugin</name>
<description>Apache NiFi Nar Plugin. It is currently a part of the Apache Incubator.</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<executions>
<execution>
<id>default-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
<phase>process-classes</phase>
</execution>
<execution>
<id>help-descriptor</id>
<goals>
<goal>helpmojo</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<type>maven-plugin</type>
</dependency>
<dependency>
<!-- No code from maven-jar-plugin is actually used; it's included
just to simplify the dependencies list. -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,202 @@
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.

View File

@ -0,0 +1,5 @@
Apache NiFi
Copyright 2014-2015 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -0,0 +1,57 @@
# Apache NiFi NAR Maven Plugin
Apache NiFi NAR Maven Plugin helps to build NiFi Archive bundles to support the classloader isolation model of NiFi.
## Table of Contents
- [Getting Started](#getting-started)
- [Getting Help](#getting-help)
- [Requirements](#requirements)
- [License](#license)
- [Disclaimer](#disclaimer)
## Getting Started
To Build:
- Executed 'mvn clean install'
## Getting Help
If you have questions, you can reach out to our mailing list: dev@nifi.incubator.apache.org
([archive](http://mail-archives.apache.org/mod_mbox/incubator-nifi-dev)).
We're also often available in IRC: #nifi on
[irc.freenode.net](http://webchat.freenode.net/?channels=#nifi).
## Requirements
* JDK 1.7 or higher
## License
Except as otherwise noted this software is licensed under the
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
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.
## Disclaimer
Apache NiFi is an effort undergoing incubation at the Apache Software
Foundation (ASF), sponsored by the Apache Incubator PMC.
Incubation is required of all newly accepted projects until a further review
indicates that the infrastructure, communications, and decision making process
have stabilized in a manner consistent with other successful ASF projects.
While incubation status is not necessarily a reflection of the completeness
or stability of the code, it does indicate that the project has yet to be
fully endorsed by the ASF.

View File

@ -20,14 +20,13 @@
<groupId>org.apache</groupId>
<artifactId>apache</artifactId>
<version>16</version>
<relativePath/>
<relativePath />
</parent>
<groupId>org.apache.nifi</groupId>
<artifactId>maven-plugins</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Apache NiFi Maven Plugins</name>
<description>Apache NiFi Maven Plugins. It is currently a part of the Apache Incubator.</description>
<artifactId>nifi-nar-maven-plugin</artifactId>
<version>1.0.0-incubating-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<description>Apache NiFi Nar Plugin. It is currently a part of the Apache Incubator.</description>
<url>http://nifi.incubator.apache.org/maven-site/</url>
<organization>
<name>Apache NiFi (incubating) Project</name>
@ -55,16 +54,11 @@
<archive>http://mail-archives.apache.org/mod_mbox/incubator-nifi-commits</archive>
</mailingList>
</mailingLists>
<prerequisites>
<maven>${maven.min-version}</maven>
</prerequisites>
<modules>
<module>nar-maven-plugin</module>
</modules>
<scm>
<connection>scm:git:git://git.apache.org/incubator-nifi.git</connection>
<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/incubator-nifi.git</developerConnection>
<url>https://git-wip-us.apache.org/repos/asf?p=incubator-nifi.git</url>
<tag>HEAD</tag>
</scm>
<issueManagement>
<system>JIRA</system>
@ -73,11 +67,13 @@
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<maven.version>3.0.5</maven.version>
<maven.min-version>3.0.5</maven.min-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<sealJars>false</sealJars>
</properties>
<prerequisites>
<maven>${maven.min-version}</maven>
</prerequisites>
<build>
<pluginManagement>
<plugins>
@ -92,20 +88,6 @@
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.9</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
@ -124,15 +106,9 @@
<artifactId>maven-assembly-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<attach>false</attach>
<tarLongFileMode>gnu</tarLongFileMode>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@ -166,22 +142,23 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<useReleaseProfile>true</useReleaseProfile>
<releaseProfiles>apache-release</releaseProfiles>
<autoVersionSubmodules>true</autoVersionSubmodules>
<goals>deploy</goals>
<tagNameFormat>@{project.artifactId}-@{project.version}</tagNameFormat>
<pushChanges>false</pushChanges>
<localCheckout>true</localCheckout>
</configuration>
<executions>
<execution>
<id>default</id>
<goals>
<goal>prepare</goal>
<goal>perform</goal>
</goals>
<configuration>
<pomFileName>platform/pom.xml</pomFileName>
<arguments>-P apache-release,check-licenses</arguments>
<autoVersionSubmodules>true</autoVersionSubmodules>
<releaseProfiles>apache-release</releaseProfiles>
<goals>deploy</goals>
<tagNameFormat>@{project.artifactId}-@{project.version}</tagNameFormat>
<pushChanges>false</pushChanges>
<localCheckout>true</localCheckout>
<pomFileName>nar-maven-plugin/pom.xml</pomFileName>
</configuration>
</execution>
</executions>
@ -189,138 +166,80 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Sealed>${sealJars}</Sealed>
<Implementation-Build>${mvngit.commit.id}</Implementation-Build>
</manifestEntries>
</archive>
</configuration>
<version>2.5</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.5</version>
<extensions>true</extensions>
<configuration>
<serverId>repository.apache.org</serverId>
<nexusUrl>https://repository.apache.org/</nexusUrl>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<artifactId>maven-plugin-plugin</artifactId>
<executions>
<execution>
<id>enforce-maven</id>
<id>default-descriptor</id>
<goals>
<goal>enforce</goal>
<goal>descriptor</goal>
</goals>
<configuration>
<rules>
<requireSameVersions>
<plugins>
<plugin>org.apache.maven.plugins:maven-surefire-plugin</plugin>
<plugin>org.apache.maven.plugins:maven-failsafe-plugin</plugin>
<plugin>org.apache.maven.plugins:maven-surefire-report-plugin</plugin>
</plugins>
</requireSameVersions>
<requireMavenVersion>
<version>${maven.version}</version>
</requireMavenVersion>
</rules>
</configuration>
<phase>process-classes</phase>
</execution>
<execution>
<id>help-descriptor</id>
<goals>
<goal>helpmojo</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<excludes>
<exclude>nb-configuration.xml</exclude>
<exclude>nbactions.xml</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.9</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.3</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<type>maven-plugin</type>
<version>2.9</version>
</dependency>
<dependency>
<!-- No code from maven-jar-plugin is actually used; it's included
just to simplify the dependencies list. -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<scope>provided</scope>
<version>3.3</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>apache-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.apache.resources</groupId>
<artifactId>apache-source-release-assembly-descriptor</artifactId>
<version>1.0.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>source-release-assembly</id>
<goals>
<goal>single</goal>
</goals>
<phase>validate</phase>
<configuration>
<runOnlyAtExecutionRoot>true</runOnlyAtExecutionRoot>
<finalName>nifi-${project.artifactId}-${project.version}</finalName>
<descriptorRefs>
<descriptorRef>source-release-zip-tar</descriptorRef>
</descriptorRefs>
<tarLongFileFormat>gnu</tarLongFileFormat>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>rename-source-release-assembly</id>
<goals>
<goal>exec</goal>
</goals>
<phase>validate</phase>
<configuration>
<executable>mv</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<commandlineArgs>-n nifi-${project.artifactId}-${project.version}-source-release.tar.gz nifi-${project.artifactId}-${project.version}-src.tar.gz</commandlineArgs>
<successCodes>
<successCode>0</successCode>
<successCode>1</successCode>
</successCodes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!-- Seal jars and skip tests when the
apache-release profile is activated. -->
<id>seal-jars</id>
<properties>
<sealJars>true</sealJars>
<skipTests>true</skipTests>
</properties>
</profile>
<profile>
<!-- Automatically check for licenses.
Activate with -P check-licenses -->

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nifi;
package org.apache.nifi;
import java.io.File;
import java.io.IOException;

View File

@ -29,7 +29,7 @@
<process-test-resources>org.apache.maven.plugins:maven-resources-plugin:testResources</process-test-resources>
<test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
<test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
<package>org.apache.nifi:nar-maven-plugin:nar</package>
<package>org.apache.nifi:nifi-nar-maven-plugin:nar</package>
<install>org.apache.maven.plugins:maven-install-plugin:install</install>
<deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
</phases>

15
nifi/DISCLAIMER Normal file
View File

@ -0,0 +1,15 @@
Apache NiFi is an effort undergoing incubation at the Apache Software
Foundation (ASF), sponsored by the Apache Incubator PMC.
Incubation is required of all newly accepted projects until a further review
indicates that the infrastructure, communications, and decision making process
have stabilized in a manner consistent with other successful ASF projects.
While incubation status is not necessarily a reflection of the completeness
or stability of the code, it does indicate that the project has yet to be
fully endorsed by the ASF.
For more information about the incubation status of the Apache NiFi project
you can go to the following page:
http://nifi.incubator.apache.org/

View File

@ -1,5 +1,5 @@
Apache NiFi
Copyright 2014 The Apache Software Foundation
Copyright 2014-2015 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

101
nifi/README.md Normal file
View File

@ -0,0 +1,101 @@
# Apache NiFi
Apache NiFi is a dataflow system based on the concepts of flow-based programming. It is currently apart of the Apache Incubator.
## Table of Contents
- [Features](#features)
- [Getting Started](#getting-started)
- [Getting Help](#getting-help)
- [Requirements](#requirements)
- [License](#license)
- [Disclaimer](#disclaimer)
- [Export Control] (#export-control)
## Features
Apache NiFi supports powerful and scalable directed graphs of data routing, transformation, and system mediation logic. Some of the high-level capabilities and objectives of Apache NiFi include:
- Web-based user interface for seamless experience between design, control, feedback, and monitoring of data flows
- Highly configurable along several dimensions of quality of service such as loss tolerant versus guaranteed delivery, low latency versus high throughput, and priority based queuing
- Fine-grained data provenance for all data received, forked, joined, cloned, modified, sent, and ultimately dropped as data reaches its configured end-state
- Component-based extension model along well defined interfaces enabling rapid development and effective testing
## Getting Started
To build:
- Execute 'mvn clean install' or for parallel build execute 'mvn -T 2.0C clean install'
To start NiFi:
- Change directory to 'nifi-assembly'. In the target directory there should be a build of nifi.
- Unpack the build wherever you like or use the already unpacked build. '<install_location>/bin/nifi.sh start'
- Direct your browser to http://localhost:8080/nifi/
## Getting Help
If you have questions, you can reach out to our mailing list: dev@nifi.incubator.apache.org
([archive](http://mail-archives.apache.org/mod_mbox/incubator-nifi-dev)).
We're also often available in IRC: #nifi on
[irc.freenode.net](http://webchat.freenode.net/?channels=#nifi).
## Requirements
* JDK 1.7 or higher
## License
Except as otherwise noted this software is licensed under the
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
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.
## Disclaimer
Apache NiFi is an effort undergoing incubation at the Apache Software
Foundation (ASF), sponsored by the Apache Incubator PMC.
Incubation is required of all newly accepted projects until a further review
indicates that the infrastructure, communications, and decision making process
have stabilized in a manner consistent with other successful ASF projects.
While incubation status is not necessarily a reflection of the completeness
or stability of the code, it does indicate that the project has yet to be
fully endorsed by the ASF.
## Export Control
This distribution includes cryptographic software. The country in which you
currently reside may have restrictions on the import, possession, use, and/or
re-export to another country, of encryption software. BEFORE using any
encryption software, please check your country's laws, regulations and
policies concerning the import, possession, or use, and re-export of encryption
software, to see if this is permitted. See <http://www.wassenaar.org/> for more
information.
The U.S. Government Department of Commerce, Bureau of Industry and Security
(BIS), has classified this software as Export Commodity Control Number (ECCN)
5D002.C.1, which includes information security software using or performing
cryptographic functions with asymmetric algorithms. The form and manner of this
Apache Software Foundation distribution makes it eligible for export under the
License Exception ENC Technology Software Unrestricted (TSU) exception (see the
BIS Export Administration Regulations, Section 740.13) for both object code and
source code.
The following provides more details on the included cryptographic software:
Apache NiFi uses BouncyCastle, Jasypt, JCraft Inc., and the built-in
java cryptography libraries for SSL, SSH, and the protection
of sensitive configuration parameters. See
http://bouncycastle.org/about.html
http://www.jasypt.org/faq.html
http://jcraft.com/c-info.html
http://www.oracle.com/us/products/export/export-regulations-345813.html
for more details on each of these libraries cryptography features.

View File

@ -1,81 +0,0 @@
<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/maven-v4_0_0.xsd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>execute-script-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>execute-script-processors</artifactId>
<description>NiFi Processors to Run Scripts</description>
<name>NiFi Script Execution Processors</name>
<dependencies>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby</artifactId>
<exclusions>
<exclusion>
<artifactId>jnr-netdb</artifactId>
<groupId>com.github.jnr</groupId>
</exclusion>
<exclusion>
<artifactId>jnr-posix</artifactId>
<groupId>com.github.jnr</groupId>
</exclusion>
<exclusion>
<artifactId>jffi</artifactId>
<groupId>com.github.jnr</groupId>
</exclusion>
<exclusion>
<artifactId>nailgun-server</artifactId>
<groupId>com.martiansoftware</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-core-flowfile-attributes</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-stream-utils</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,566 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.script;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.script.ScriptException;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.io.BufferedInputStream;
import org.apache.nifi.io.BufferedOutputStream;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.annotation.CapabilityDescription;
import org.apache.nifi.processor.annotation.EventDriven;
import org.apache.nifi.processor.annotation.Tags;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.InputStreamCallback;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.scripting.ConverterScript;
import org.apache.nifi.scripting.ReaderScript;
import org.apache.nifi.scripting.Script;
import org.apache.nifi.scripting.ScriptFactory;
import org.apache.nifi.scripting.WriterScript;
/**
* <!-- Processor Documentation ================================================== -->
* <h2>Description:</h2>
* <p>
* This processor provides the capability to execute scripts in various
* scripting languages, and passes into the scripts the input stream and output
* stream(s) representing an incoming flow file and any created flow files. The
* processor is designed to be thread safe, so multiple concurrent tasks may
* execute against a single script. The processor provides a framework which
* enables script writers to implement 3 different types of scripts:
* <ul>
* ReaderScript - which enables stream-based reading of a FlowFile's
* content</br> WriterScript - which enables stream-based reading and
* writing/modifying of a FlowFile's content</br> ConverterScript - which
* enables stream-based reading a FlowFile's content and stream-based writing to
* newly created FlowFiles</br>
* </ul>
* Presently, the processor supports 3 scripting languages: Ruby, Python, and
* JavaScript. The processor is built on the javax.script API which enables
* ScriptEngine discovery, thread management, and encapsulates much of the low
* level bridging-code that enables Java to Script language integration. Thus,
* it is designed to be easily extended to other scripting languages. </br> The
* attributes of a FlowFile and properties of the Processor are exposed to the
* script by either a variable in the base class or a getter method. A script
* may declare new Processor Properties and different Relationships via
* overriding the getPropertyDescriptors and getRelationships methods,
* respectively.
* </p>
* <p>
* <strong>Properties:</strong>
* </p>
* <p>
* In the list below, the names of required properties appear in bold. Any other
* properties (not in bold) are considered optional. If a property has a default
* value, it is indicated. If a property supports the use of the NiFi Expression
* Language (or simply, "expression language"), that is also indicated. Of
* particular note: This processor allows scripts to define additional Processor
* properties, which will not be initially visible. Once the processor's
* configuration is validated, script defined properties will become visible,
* and may affect the validity of the processor.
* </p>
* <ul>
* <li>
* <strong>Script File Name</strong>
* <ul>
* <li>Script location, can be relative or absolute path.</li>
* <li>Default value: no default</li>
* <li>Supports expression language: false</li>
* </ul>
* </li>
* <li>
* <strong>Script Check Interval</strong>
* <ul>
* <li>The time period between checking for updates to a script.</li>
* <li>Default value: 15 sec</li>
* <li>Supports expression language: false</li>
* </ul>
* </li>
* </ul>
*
* <p>
* <strong>Relationships:</strong>
* </p>
* <p>
* The initial 'out of the box' relationships are below. Of particular note is
* the ability of a script to change the set of relationships. However, any
* relationships defined by the script will not be visible until the processor's
* configuration has been validated. Once done, new relationships will become
* visible.
* </p>
* <ul>
* <li>
* success
* <ul>
* <li>Used when a file is successfully processed by a script.</li>
* </ul>
* </li>
* <li>
* failure
* <ul>
* <li>Used when an error occurs while processing a file with a script.</li>
* </ul>
* </li>
* </ul>
*
* <p>
* <strong>Example Scripts:</strong>
* </p>
* <ul>
* JavaScript example - the 'with' statement imports packages defined in the
* framework. Since the 'instance' variable is intended to be local scope (not
* global), it must be named 'instance' as it it not passed back to the
* processor upon script evaluation and must be fetched. If you make it global,
* you can name it whatever you'd like...but this is intended to be
* multi-threaded so do so at your own risk. Presently, there are issues with
* the JavaScript scripting engine that prevent sub-classing the base classes in
* the Processor's Java framework. So, what is actually happening is an instance
* of the ReaderScript is created with a provided callback object. When we are
* able to move to a more competent scripting engine, the code below will remain
* the same, but the 'instance' variable will actually be a sub-class of
* ReaderScript.
*
* <pre>
* with (Scripting) {
* var instance = new ReaderScript({
* route : function(input) {
* var str = IOUtils.toString(input);
* var expr = instance.getProperty("expr");
* filename = instance.attributes.get("filename");
* instance.setAttribute("filename", filename + ".modified");
* if (str.match(expr)) {
* return Script.FAIL_RELATIONSHIP;
* } else {
* return Script.SUCCESS_RELATIONSHIP;
* }
* }
* });
* }
* </pre>
*
* Ruby example - the 'OutputStreamHandler' is an interface which is called when
* creating flow files.
*
* <pre>
* java_import 'org.apache.nifi.scripting.OutputStreamHandler'
* class SimpleConverter < ConverterScript
* field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :logger, :attributes
*
* def convert(input)
* in_io = input.to_io
* createFlowFile("firstLine", FAIL_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
* out_io = out.to_io
* out_io << in_io.readline.to_java_bytes
* out_io.close
* logger.debug("Wrote data to failure...this message logged with logger from super class")
* end)
*
* createFlowFile("otherLines", SUCCESS_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
* out_io = out.to_io
* in_io.each_line { |line|
* out_io << line
* }
* out_io.close
* logger.debug("Wrote data to success...this message logged with logger from super class")
* end)
* in_io.close
* end
*
* end
*
* $logger.debug("Creating SimpleConverter...this message logged with logger from shared variables")
* SimpleConverter.new
* </pre>
*
* Python example - The difficulty with Python is that it does not return
* objects upon script evaluation, so the instance of the Script class must be
* fetched by name. Thus, you must define a variable called 'instance'.
*
* <pre>
* import re
*
* class RoutingReader(ReaderScript):
* A = Relationship.Builder().name("a").description("some good stuff").build()
* B = Relationship.Builder().name("b").description("some other stuff").build()
* C = Relationship.Builder().name("c").description("some bad stuff").build()
*
* def getRelationships(self):
* return [self.A,self.B,self.C]
*
* def getExceptionRoute(self):
* return self.C
*
* def route( self, input ):
* for line in FileUtil.wrap(input):
* if re.match("^bad", line, re.IGNORECASE):
* return self.B
* if re.match("^sed", line):
* raise RuntimeError("That's no good!")
*
* return self.A
*
* instance = RoutingReader()
* </pre>
*
* </ul>
* <p>
* <strong>Shared Variables</strong>
* </p>
* <ul>
* <li>logger : global scope</li>
* <li>properties : local/instance scope</li>
* </ul>
* <p>
* <strong>Script API:</strong>
* </p>
* <ul>
* <li>getAttribute(String) : String</li>
* <li>getAttributes() : Map(String,String)</li>
* <li>getExceptionRoute() : Relationship</li>
* <li>getFileName() : String</li>
* <li>getFlowFileEntryDate() : Calendar</li>
* <li>getFlowFileSize() : long</li>
* <li>getProperties() : Map(String, String)</li>
* <li>getProperty(String) : String</li>
* <li>getPropertyDescriptors() : List(PropertyDescriptor)</li>
* <li>getRelationships() : Collection(Relationship)</li>
* <li>getRoute() : Relationship</li>
* <li>setRoute(Relationship)</li>
* <li>setAttribute(String, String)</li>
* <li>validate() : Collection(String)</li>
* </ul>
* <p>
* <strong>ReaderScript API:</strong>
* </p>
* <ul>
* <li>route(InputStream) : Relationship</li>
* </ul>
* <p>
* <strong>WriterScript API:</strong>
* </p>
* <ul>
* <li>process(InputStream, OutputStream)</li>
* </ul>
* <p>
* <strong>ConverterScript API:</strong>
* </p>
* <ul>
* <li>convert(InputStream)</li>
* <li>createFlowFile(String, Relationship, OutputStreamHandler)</li>
* </ul>
* <p>
* <strong>OutputStreamHandler API:</strong>
* </p>
* <ul>
* <li>write(OutputStream)</li>
* </ul>
*/
@EventDriven
@Tags({"script", "ruby", "python", "javascript", "execute"})
@CapabilityDescription("Execute scripts in various scripting languages, and passes into the scripts the input stream and output stream(s) "
+ "representing an incoming flow file and any created flow files.")
public class ExecuteScript extends AbstractProcessor {
private final AtomicBoolean doCustomValidate = new AtomicBoolean(true);
private final AtomicReference<Set<Relationship>> relationships = new AtomicReference<>();
private final AtomicReference<List<PropertyDescriptor>> propertyDescriptors = new AtomicReference<>();
private volatile ScriptFactory scriptFactory;
private volatile Relationship exceptionRoute;
/**
* Script location, can be relative or absolute path -- passed as-is to
* {@link File#File(String) File constructor}
*/
public static final PropertyDescriptor SCRIPT_FILE_NAME = new PropertyDescriptor.Builder()
.name("Script File Name")
.description("Script location, can be relative or absolute path")
.required(true)
.addValidator(new Validator() {
@Override
public ValidationResult validate(String subject, String input, ValidationContext context) {
ValidationResult result = StandardValidators.FILE_EXISTS_VALIDATOR.validate(subject, input, context);
if (result.isValid()) {
int dotPos = input.lastIndexOf('.');
if (dotPos < 1) {
result = new ValidationResult.Builder()
.subject(subject)
.valid(false)
.explanation("Filename must have an extension")
.input(input)
.build();
}
}
return result;
}
})
.build();
static final PropertyDescriptor SCRIPT_CHECK_INTERVAL = new PropertyDescriptor.Builder()
.name("Script Check Interval")
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
.description("The time period between checking for updates to a script")
.required(true)
.defaultValue("15 sec")
.build();
@Override
protected void init(ProcessorInitializationContext context) {
Set<Relationship> empty = Collections.emptySet();
relationships.set(empty);
ArrayList<PropertyDescriptor> propDescs = new ArrayList<>();
propDescs.add(SCRIPT_FILE_NAME);
propDescs.add(SCRIPT_CHECK_INTERVAL);
propertyDescriptors.set(Collections.unmodifiableList(propDescs));
scriptFactory = new ScriptFactory(getLogger());
}
@Override
public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return propertyDescriptors.get();
}
@Override
protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(String propertyDescriptorName) {
return new PropertyDescriptor.Builder()
.name(propertyDescriptorName)
.dynamic(true)
.addValidator(Validator.VALID)
.build();
}
@Override
public void onPropertyModified(PropertyDescriptor descriptor, String oldValue, String newValue) {
doCustomValidate.set(true);
}
@Override
public Set<Relationship> getRelationships() {
return relationships.get();
}
/**
* Called by framework.
*
* Returns a list of reasons why this processor cannot be run.
* @return
*/
@Override
protected Collection<ValidationResult> customValidate(ValidationContext validationContext) {
if (doCustomValidate.getAndSet(false)) {
long interval = validationContext.getProperty(SCRIPT_CHECK_INTERVAL).asTimePeriod(TimeUnit.MILLISECONDS);
scriptFactory.setScriptCheckIntervalMS(interval);
List<ValidationResult> results = new ArrayList<>();
String file = validationContext.getProperty(SCRIPT_FILE_NAME).getValue();
try {
Script s = scriptFactory.getScript(file);
// set the relationships of the processor
relationships.set(new HashSet<>(s.getRelationships()));
// need to get script's prop. descs. and validate. May, or may not, have dynamic
// props already...depends if this is the first time the processor is being configured.
Map<PropertyDescriptor, String> properties = validationContext.getProperties();
// need to compare props, if any, against script-expected props that are required.
// script may be expecting required props that are not known, or some props may have invalid
// values.
// processor may be configured with dynamic props that the script will use...but does not declare which would
// be a bad thing
List<PropertyDescriptor> scriptPropDescs = s.getPropertyDescriptors();
getLogger().debug("Script is {}", new Object[]{s});
getLogger().debug("Script file name is {}", new Object[]{s.getFileName()});
getLogger().debug("Script Prop Descs are: {}", new Object[]{scriptPropDescs.toString()});
getLogger().debug("Thread is: {}", new Object[]{Thread.currentThread().toString()});
for (PropertyDescriptor propDesc : scriptPropDescs) {
// need to check for missing props
if (propDesc.isRequired() && !properties.containsKey(propDesc)) {
results.add(new ValidationResult.Builder()
.subject("Script Properties")
.valid(false)
.explanation("Missing Property " + propDesc.getName())
.build());
// need to validate current value against script provided validator
} else if (properties.containsKey(propDesc)) {
String value = properties.get(propDesc);
ValidationResult result = propDesc.validate(value, validationContext);
if (!result.isValid()) {
results.add(result);
}
} // else it is an optional prop according to the script and it is not specified by
// the configuration of the processor
}
// need to update the known prop desc's with what we just got from the script
List<PropertyDescriptor> pds = new ArrayList<>(propertyDescriptors.get());
pds.addAll(scriptPropDescs);
propertyDescriptors.set(Collections.unmodifiableList(pds));
if (results.isEmpty()) {
// so needed props are supplied and individually validated, now validate script
Collection<String> reasons;
reasons = s.validate();
if (null == reasons) {
getLogger().warn("Script had invalid return value for validate(), ignoring.");
} else {
for (String reason : reasons) {
ValidationResult result = new ValidationResult.Builder()
.subject("ScriptValidation")
.valid(false)
.explanation(reason)
.build();
results.add(result);
}
}
}
// get the exception route
exceptionRoute = s.getExceptionRoute();
return results;
} catch (ScriptException | IOException | NoSuchMethodException e) {
doCustomValidate.set(true);
results.add(new ValidationResult.Builder()
.subject("ScriptValidation")
.valid(false)
.explanation("Cannot create script due to " + e.getMessage())
.input(file)
.build());
getLogger().error("Cannot create script due to " + e, e);
return results;
}
}
return null;
}
@Override
public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException {
FlowFile flowFile = session.get();
if (flowFile == null) {
return; // fail-fast if there is no work to do
}
final String scriptFileName = context.getProperty(SCRIPT_FILE_NAME).getValue();
// doing this cloning because getProperties does not initialize props that have only their default values
// must do a getProperty for that value to be initialized
Map<String, String> props = new HashMap<>();
for (PropertyDescriptor propDesc : context.getProperties().keySet()) {
if (propDesc.isExpressionLanguageSupported()) {
props.put(propDesc.getName(), context.getProperty(propDesc).evaluateAttributeExpressions(flowFile).getValue());
} else {
props.put(propDesc.getName(), context.getProperty(propDesc).getValue());
}
}
Script script = null;
try {
final Script finalScript = scriptFactory.getScript(scriptFileName, props, flowFile);
script = finalScript;
if (finalScript instanceof ReaderScript) {
session.read(flowFile, new InputStreamCallback() {
@Override
public void process(InputStream in) throws IOException {
try {
((ReaderScript) finalScript).process(new BufferedInputStream(in));
} catch (NoSuchMethodException | ScriptException e) {
getLogger().error("Failed to execute ReaderScript", e);
throw new IOException(e);
}
}
});
} else if (finalScript instanceof WriterScript) {
flowFile = session.write(flowFile, new StreamCallback() {
@Override
public void process(InputStream in, OutputStream out) throws IOException {
try {
((WriterScript) finalScript).process(new BufferedInputStream(in), new BufferedOutputStream(out));
out.flush();
} catch (NoSuchMethodException | ScriptException e) {
getLogger().error("Failed to execute WriterScript", e);
throw new IOException(e);
}
}
});
} else if (finalScript instanceof ConverterScript) {
((ConverterScript) finalScript).process(session);
// Note that these scripts don't pass the incoming FF through,
// they always create new outputs
session.remove(flowFile);
return;
} else {
// only thing we can do is assume script has already run and done it's thing, so just transfer the incoming
// flowfile
getLogger().debug("Successfully executed script from {}", new Object[]{scriptFileName});
}
// update flow file attributes
flowFile = session.putAllAttributes(flowFile, finalScript.getAttributes());
Relationship route = finalScript.getRoute();
if (null == route) {
session.remove(flowFile);
getLogger().info("Removing flowfile {}", new Object[]{flowFile});
} else {
session.transfer(flowFile, route);
getLogger().info("Transferring flowfile {} to {}", new Object[]{flowFile, route});
}
} catch (ScriptException | IOException e) {
getLogger().error("Failed to create script from {} with flowFile {}. Rolling back session.",
new Object[]{scriptFileName, flowFile}, e);
throw new ProcessException(e);
} catch (Exception e) {
if (null != script) {
getLogger().error("Failed to execute script from {}. Transferring flow file {} to {}",
new Object[]{scriptFileName, flowFile, exceptionRoute}, e);
session.transfer(flowFile, exceptionRoute);
} else {
getLogger().error("Failed to execute script from {} with flowFile {}. Rolling back session",
new Object[]{scriptFileName, flowFile}, e);
throw new ProcessException(e);
}
}
}
}

View File

@ -1,131 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptException;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.io.BufferedInputStream;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.io.InputStreamCallback;
import org.apache.nifi.processor.io.OutputStreamCallback;
/**
* <p>
* Script authors should extend this class if they want to perform complex
* conversions in a NiFi processor.
* </p>
*
* <p>
* Scripts must implement {@link #convert(FileInputStream)}. This method may
* create new FlowFiles and pass them to one or more routes. The input FlowFile
* will be removed from the repository after execution of this method completes.
* </p>
*
* <p>
* In general, the {@link #convert(FileInputStream)} will read from the supplied
* stream, then create one or more output sinks and route the result to the
* relationship of choice using
* {@link #routeStream(ByteArrayOutputStream, String, String)} or
* {@link #routeBytes(byte[], String, String)}.
*
* <p>
* Implement {@link #getProcessorRelationships()} to allow writing to
* relationships other than <code>success</code> and <code>failure</code>. The
* {@link #getRoute()} superclass method is *not* used by Converter Scripts.
* </p>
*
*/
public class ConverterScript extends Script {
private ProcessSession session; // used to create files
private Object convertCallback;
public ConverterScript() {
}
public ConverterScript(Object... callbacks) {
super(callbacks);
for (Object callback : callbacks) {
if (callback instanceof Map<?, ?>) {
convertCallback = convertCallback == null && ((Map<?, ?>) callback).containsKey("convert") ? callback : convertCallback;
}
}
}
// Subclasses should implement this to define basic logic
protected void convert(InputStream stream) throws NoSuchMethodException, ScriptException {
if (convertCallback != null) {
((Invocable) engine).invokeMethod(convertCallback, "convert", stream);
}
}
/**
* Owning processor uses this method to kick off handling of a single file
*
* @param aSession the owning processor's Repository (needed to make new
* files)
*/
public void process(ProcessSession aSession) {
this.session = aSession;
this.session.read(this.flowFile, new InputStreamCallback() {
@Override
public void process(InputStream in) throws IOException {
BufferedInputStream stream = new BufferedInputStream(in);
try {
convert(stream);
} catch (NoSuchMethodException | ScriptException e) {
logger.error("Failed to execute 'convert' function in script", e);
throw new IOException(e);
}
}
});
}
// this should go back to protected once we get Nashorn
public void createFlowFile(final String flowFileName, final Relationship relationship, final OutputStreamHandler handler) {
FlowFile result = session.create(this.flowFile);
result = session.putAttribute(result, CoreAttributes.FILENAME.key(), flowFileName);
try {
result = session.write(result, new OutputStreamCallback() {
@Override
public void process(OutputStream out) throws IOException {
handler.write(out);
}
});
this.logger.info("Transfer flow file {} to {}", new Object[]{result, relationship});
session.transfer(result, relationship);
} catch (Exception e) {
this.logger.error("Could not create new flow file from script", e);
session.remove(result);
}
}
}

View File

@ -1,46 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
public enum JRubyScriptFactory {
INSTANCE;
private static final String PRELOADS = "include Java\n"
+ "\n"
+ "java_import 'org.apache.nifi.components.PropertyDescriptor'\n"
+ "java_import 'org.apache.nifi.components.Validator'\n"
+ "java_import 'org.apache.nifi.processor.util.StandardValidators'\n"
+ "java_import 'org.apache.nifi.processor.Relationship'\n"
+ "java_import 'org.apache.nifi.logging.ProcessorLog'\n"
+ "java_import 'org.apache.nifi.scripting.ReaderScript'\n"
+ "java_import 'org.apache.nifi.scripting.WriterScript'\n"
+ "java_import 'org.apache.nifi.scripting.ConverterScript'\n"
+ "\n";
public String getScript(File scriptFile) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append(PRELOADS)
.append(FileUtils.readFileToString(scriptFile, "UTF-8"));
return sb.toString();
}
}

View File

@ -1,56 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
public enum JavaScriptScriptFactory {
INSTANCE;
private static final String PRELOADS = "var Scripting = JavaImporter(\n"
+ " Packages.org.apache.nifi.components,\n"
+ " Packages.org.apache.nifi.processor.util,\n"
+ " Packages.org.apache.nifi.processor,\n"
+ " Packages.org.apache.nifi.logging,\n"
+ " Packages.org.apache.nifi.scripting,\n"
+ " Packages.org.apache.commons.io);\n"
+ "var readFile = function (file) {\n"
+ " var script = Packages.org.apache.commons.io.FileUtils.readFileToString("
+ " new java.io.File($PATH, file)"
+ " );\n"
+ " return \"\" + script;\n"
+ "}\n"
+ "var require = function (file){\n"
+ " var exports={}, module={};\n"
+ " module.__defineGetter__('id', function(){return file;});"
+ " eval(readFile(file));\n"
+ " return exports;\n"
+ "}\n";
public String getScript(File scriptFile) throws IOException {
StringBuilder sb = new StringBuilder();
final String parent = StringUtils.replace(scriptFile.getParent(), "\\", "/");
sb.append(PRELOADS).append("var $PATH = \"").append(parent).append("\"\n")
.append(FileUtils.readFileToString(scriptFile, "UTF-8"));
return sb.toString();
}
}

View File

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
public enum JythonScriptFactory {
INSTANCE;
private final static String PRELOADS = "from org.python.core.util import FileUtil\n"
+ "from org.apache.nifi.components import PropertyDescriptor\n"
+ "from org.apache.nifi.components import Validator\n"
+ "from org.apache.nifi.processor.util import StandardValidators\n"
+ "from org.apache.nifi.processor import Relationship\n"
+ "from org.apache.nifi.logging import ProcessorLog\n"
+ "from org.apache.nifi.scripting import ReaderScript\n"
+ "from org.apache.nifi.scripting import WriterScript\n"
+ "from org.apache.nifi.scripting import ConverterScript\n";
public String getScript(File scriptFile) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append(PRELOADS)
.append(FileUtils.readFileToString(scriptFile, "UTF-8"));
return sb.toString();
}
}

View File

@ -1,24 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.OutputStream;
public interface OutputStreamHandler {
void write(OutputStream out);
}

View File

@ -1,79 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.InputStream;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptException;
import org.apache.nifi.processor.Relationship;
/**
* <p>
* Script authors should extend this class if they want to follow the "reader"
* paradigm for NiFi processors.
* </p>
*
* <p>
* User scripts should implement {@link #route(InputStream)}. <code>route</code>
* uses a returned relationship name to determine where FlowFiles go. Scripts
* may also implement {@link #getProcessorRelationships()} to specify available
* relationship names.
* </p>
*
*/
public class ReaderScript extends Script {
private Object routeCallback;
public ReaderScript(Object... callbacks) {
super(callbacks);
for (Object callback : callbacks) {
if (callback instanceof Map<?, ?>) {
routeCallback = routeCallback == null && ((Map<?, ?>) callback).containsKey("route") ? callback : routeCallback;
}
}
}
public ReaderScript() {
}
// Simple helper
public void process(InputStream input) throws NoSuchMethodException, ScriptException {
lastRoute = route(input);
}
/**
* Subclasses should examine the provided inputstream, then determine which
* relationship the file will be sent down and return its name.
*
*
* @param in a Java InputStream containing the incoming FlowFile.
* @return a relationship name
* @throws ScriptException
* @throws NoSuchMethodException
*/
public Relationship route(InputStream in) throws NoSuchMethodException, ScriptException {
Relationship relationship = null;
Invocable invocable = (Invocable) this.engine;
relationship = (Relationship) invocable.invokeMethod(routeCallback, "route", in);
return relationship;
}
}

View File

@ -1,303 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.logging.ProcessorLog;
import org.apache.nifi.processor.Relationship;
/**
* <p>
* Base class for all scripts. In this framework, only ScriptEngines that
* implement javax.script.Invocable are supported.
*
* </p>
*
*/
public class Script {
public static final Relationship SUCCESS_RELATIONSHIP = new Relationship.Builder()
.name("success")
.description("Destination of successfully created flow files")
.build();
public static final Relationship FAIL_RELATIONSHIP = new Relationship.Builder()
.name("failure")
.description("Destination of flow files when a error occurs in the script")
.build();
static final Set<Relationship> RELATIONSHIPS;
static {
Set<Relationship> rels = new HashSet<>();
rels.add(FAIL_RELATIONSHIP);
rels.add(SUCCESS_RELATIONSHIP);
RELATIONSHIPS = Collections.unmodifiableSet(rels);
}
FlowFile flowFile = null;
ScriptEngine engine = null;
protected Map<String, String> properties = new HashMap<>();
protected Relationship lastRoute = SUCCESS_RELATIONSHIP;
protected ProcessorLog logger;
protected String scriptFileName;
protected Map<String, String> attributes = new HashMap<>();
protected long flowFileSize = 0;
protected long flowFileEntryDate = System.currentTimeMillis();
// the following are needed due to an inadequate JavaScript ScriptEngine. It will not allow
// subclassing a Java Class, only implementing a Java Interface. So, the syntax of JavaScript
// scripts looks like subclassing, but actually is just constructing a Script instance and
// passing in functions as args to the constructor. When we move to Nashorn JavaScript ScriptEngine
// in Java 8, we can get rid of these and revert the subclasses of this class to abstract.
protected Object propDescCallback;
protected Object relationshipsCallback;
protected Object validateCallback;
protected Object exceptionRouteCallback;
/**
* Create a Script without any parameters
*/
public Script() {
}
public Script(Object... callbacks) {
for (Object callback : callbacks) {
if (callback instanceof Map<?, ?>) {
propDescCallback = propDescCallback == null && ((Map<?, ?>) callback).containsKey("getPropertyDescriptors") ? callback
: propDescCallback;
relationshipsCallback = relationshipsCallback == null && ((Map<?, ?>) callback).containsKey("getRelationships") ? callback
: relationshipsCallback;
validateCallback = validateCallback == null && ((Map<?, ?>) callback).containsKey("validate") ? callback : validateCallback;
exceptionRouteCallback = exceptionRouteCallback == null && ((Map<?, ?>) callback).containsKey("getExceptionRoute") ? callback
: exceptionRouteCallback;
}
}
}
/**
* Specify a set of properties with corresponding NiFi validators.
*
* Subclasses that do not override this method will still have access to all
* properties via the "properties" field
*
* @return a list of PropertyDescriptors
* @throws ScriptException
* @throws NoSuchMethodException
*/
@SuppressWarnings("unchecked")
public List<PropertyDescriptor> getPropertyDescriptors() throws NoSuchMethodException, ScriptException {
if (propDescCallback != null) {
return (List<PropertyDescriptor>) ((Invocable) engine).invokeMethod(propDescCallback, "getPropertyDescriptors", (Object) null);
}
return Collections.emptyList();
}
/**
* Specify a set of reasons why this processor should be invalid.
*
* Subclasses that do not override this method will depend only on
* individual property validators as specified in
* {@link #getPropertyDescriptors()}.
*
* @return a Collection of messages to display to the user, or an empty
* Collection if the processor configuration is OK.
* @throws ScriptException
* @throws NoSuchMethodException
*/
@SuppressWarnings("unchecked")
public Collection<String> validate() throws NoSuchMethodException, ScriptException {
if (validateCallback != null) {
return (Collection<String>) ((Invocable) engine).invokeMethod(validateCallback, "validate", (Object) null);
}
return Collections.emptyList();
}
void setFlowFile(FlowFile ff) {
flowFile = ff;
if (null != ff) {
// have to clone because ff.getAttributes is unmodifiable
this.attributes = new HashMap<>(ff.getAttributes());
this.flowFileSize = ff.getSize();
this.flowFileEntryDate = ff.getEntryDate();
}
}
void setProperties(Map<String, String> map) {
properties = new HashMap<>(map);
}
/**
* Required to access entire properties map -- Jython (at least) won't let
* you read the member variable without a getter
*
* @return entire parameter map
*/
// change back to protected when we get nashorn
public Map<String, String> getProperties() {
return properties;
}
/**
* Get the named parameter. Some scripting languages make a method call
* easier than accessing a member field, so this is a convenience method to
* look up values in the properties field.
*
* @param key a hash key
* @return the value pointed at by the key specified
*/
public String getProperty(String key) {
return properties.get(key);
}
/**
* Name the various relationships by which a file can leave this processor.
* Subclasses may override this method to change available relationships.
*
* @return a collection of relationship names
* @throws ScriptException
* @throws NoSuchMethodException
*/
@SuppressWarnings("unchecked")
public Collection<Relationship> getRelationships() throws NoSuchMethodException, ScriptException {
if (relationshipsCallback != null) {
return (Collection<Relationship>) ((Invocable) engine).invokeMethod(relationshipsCallback, "getRelationships", (Object) null);
}
return RELATIONSHIPS;
}
/**
* Determine what do with a file that has just been processed.
*
* After a script runs its "read" or "write" method, it should update the
* "lastRoute" field to specify the relationship to which the resulting file
* will be sent.
*
* @return a relationship name
*/
public Relationship getRoute() {
return lastRoute;
}
// Required because of a potential issue in Rhino -- protected methods are visible in
// subclasses but protected fields (like "lastRoute") are not
// change back to protected when we get nashorn
public void setRoute(Relationship route) {
lastRoute = route;
}
/**
* Determine where to send a file if an exception is thrown during
* processing.
*
* Subclasses may override this method to use a different relationship, or
* to determine the relationship dynamically. Returning null causes the file
* to be deleted instead.
*
* Defaults to "failure".
*
* @return the name of the relationship to use in event of an exception, or
* null to delete the file.
* @throws ScriptException
* @throws NoSuchMethodException
*/
public Relationship getExceptionRoute() throws NoSuchMethodException, ScriptException {
if (exceptionRouteCallback != null) {
return (Relationship) ((Invocable) engine).invokeMethod(exceptionRouteCallback, "getExceptionRoute", (Object) null);
}
return FAIL_RELATIONSHIP;
}
/*
* Some scripting languages make a method call easier than accessing a member field, so this is a convenience method to get
* the incoming flow file size.
*/
// Change back to protected when we get nashorn
public long getFlowFileSize() {
return flowFileSize;
}
/*
* Some scripting languages make a method call easier than accessing a member field, so this is a convenience method to get
* entry date of the flow file.
*/
// Change back to protected when we get nashorn
public long getFlowFileEntryDate() {
return flowFileEntryDate;
}
void setLogger(ProcessorLog logger) {
this.logger = logger;
}
/*
* Required so that scripts in some languages can read access the attribute. Jython (at least) won't let you read the member
* variable without a getter
*/
protected ProcessorLog getLogger() {
return this.logger;
}
void setFileName(String scriptFileName) {
this.scriptFileName = scriptFileName;
}
public String getFileName() {
return this.scriptFileName;
}
// this one's public because it's needed by ExecuteScript to update the flow file's attributes AFTER processing is done
public Map<String, String> getAttributes() {
return this.attributes;
}
/*
* Some scripting languages make a method call easier than accessing a member field, so this is a convenience method to look
* up values in the attributes field.
*/
// Change back to protected when we get nashorn
public String getAttribute(String key) {
return this.attributes.get(key);
}
/*
* Some scripting languages make a method call easier than accessing a member field, so this is a convenience method to set
* key/value pairs in the attributes field.
*/
// Change back to protected when we get nashorn
public void setAttribute(String key, String value) {
this.attributes.put(key, value);
}
void setEngine(ScriptEngine scriptEngine) {
this.engine = scriptEngine;
}
}

View File

@ -1,117 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang3.StringUtils;
import org.jruby.embed.PropertyName;
public class ScriptEngineFactory {
private static final String THREADING = "THREADING";
private static final String MULTITHREADED = "MULTITHREADED";
private static final String STATELESS = "STATELESS";
private static final String THREAD_ISOLATED = "THREAD-ISOLATED";
final static ScriptEngineManager scriptEngMgr;
static {
System.setProperty(PropertyName.LOCALCONTEXT_SCOPE.toString(), "singlethread");
System.setProperty(PropertyName.COMPILEMODE.toString(), "jit");
System.setProperty(PropertyName.COMPATVERSION.toString(), "JRuby1.9");
System.setProperty(PropertyName.LOCALVARIABLE_BEHAVIOR.toString(), "transient");
System.setProperty("compile.invokedynamic", "false");
System.setProperty(PropertyName.LAZINESS.toString(), "true");
scriptEngMgr = new ScriptEngineManager();
}
final ConcurrentHashMap<String, ScriptEngine> threadSafeEngines = new ConcurrentHashMap<>();
ScriptEngine getEngine(String extension) {
ScriptEngine engine = threadSafeEngines.get(extension);
if (null == engine) {
engine = scriptEngMgr.getEngineByExtension(extension);
if (null == engine) {
throw new IllegalArgumentException("No ScriptEngine exists for extension " + extension);
}
Object threading = engine.getFactory().getParameter(THREADING);
// the MULTITHREADED status means that the scripts need to be careful about sharing state
if (THREAD_ISOLATED.equals(threading) || STATELESS.equals(threading) || MULTITHREADED.equals(threading)) {
ScriptEngine cachedEngine = threadSafeEngines.putIfAbsent(extension, engine);
if (null != cachedEngine) {
engine = cachedEngine;
}
}
}
return engine;
}
ScriptEngine getNewEngine(File scriptFile, String extension) throws ScriptException {
ScriptEngine engine = scriptEngMgr.getEngineByExtension(extension);
if (null == engine) {
throw new IllegalArgumentException("No ScriptEngine exists for extension " + extension);
}
// Initialize some paths
StringBuilder sb = new StringBuilder();
switch (extension) {
case "rb":
String parent = scriptFile.getParent();
parent = StringUtils.replace(parent, "\\", "/");
sb.append("$:.unshift '")
.append(parent)
.append("'\n")
.append("$:.unshift File.join '")
.append(parent)
.append("', 'lib'\n");
engine.eval(sb.toString());
break;
case "py":
parent = scriptFile.getParent();
parent = StringUtils.replace(parent, "\\", "/");
String lib = parent + "/lib";
sb.append("import sys\n").append("sys.path.append('").append(parent)
.append("')\n").append("sys.path.append('")
.append(lib)
.append("')\n")
.append("__file__ = '")
.append(scriptFile.getAbsolutePath())
.append("'\n");
engine.eval(sb.toString());
break;
default:
break;
}
Object threading = engine.getFactory().getParameter(THREADING);
// the MULTITHREADED status means that the scripts need to be careful about sharing state
if (THREAD_ISOLATED.equals(threading) || STATELESS.equals(threading) || MULTITHREADED.equals(threading)) {
// replace prior instance if any
threadSafeEngines.put(extension, engine);
}
return engine;
}
boolean isThreadSafe(String scriptExtension) {
return threadSafeEngines.containsKey(scriptExtension);
}
}

View File

@ -1,269 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.io.BufferedInputStream;
import org.apache.nifi.logging.ProcessorLog;
import org.apache.commons.io.FileUtils;
/**
* While this is a 'factory', it is not a singleton because we want a factory
* per processor. This factory has state, all of which belong to only one
* processor.
*
*/
public class ScriptFactory {
private final ScriptEngineFactory engineFactory = new ScriptEngineFactory();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReadLock readLock = lock.readLock();
private final WriteLock writeLock = lock.writeLock();
private final ProcessorLog logger;
private volatile CompiledScript compiledScript;
private volatile String scriptText;
private volatile byte[] md5Hash;
private volatile long lastTimeChecked;
private volatile String scriptFileName;
private volatile long scriptCheckIntervalMS = 15000;
public ScriptFactory(ProcessorLog logger) {
this.logger = logger;
}
public void setScriptCheckIntervalMS(long msecs) {
this.scriptCheckIntervalMS = msecs;
}
/**
* @param aScriptFileName
* @param properties
* @param flowFile
* @return
* @throws IOException
* @throws ScriptException
*/
public Script getScript(final String aScriptFileName, final Map<String, String> properties, final FlowFile flowFile)
throws IOException, ScriptException {
final Script instance;
long now = System.currentTimeMillis();
readLock.lock();
try {
if (!aScriptFileName.equals(this.scriptFileName)) {
readLock.unlock();
writeLock.lock();
try {
if (!aScriptFileName.equals(this.scriptFileName)) {
// need to get brand new engine
compiledScript = null;
this.md5Hash = getMD5Hash(aScriptFileName);
this.lastTimeChecked = now;
this.scriptFileName = aScriptFileName;
updateEngine();
} // else another thread beat me to the change...so just get a script
} finally {
readLock.lock();
writeLock.unlock();
}
} else if (lastTimeChecked + scriptCheckIntervalMS < now) {
readLock.unlock();
writeLock.lock();
try {
if (lastTimeChecked + scriptCheckIntervalMS < now) {
byte[] md5 = getMD5Hash(this.scriptFileName);
if (!MessageDigest.isEqual(md5Hash, md5)) {
// need to get brand new engine
compiledScript = null;
updateEngine();
this.md5Hash = md5;
} // else no change to script, so just update time checked
this.lastTimeChecked = now;
} // else another thread beat me to the check...so just get a script
} finally {
readLock.lock();
writeLock.unlock();
}
}
try {
instance = getScriptInstance(properties);
instance.setFileName(this.scriptFileName);
instance.setProperties(properties);
instance.setLogger(logger);
instance.setFlowFile(flowFile);
} catch (ScriptException e) {
// need to reset state to enable re-initialization
this.lastTimeChecked = 0;
this.scriptFileName = null;
throw e;
}
} finally {
readLock.unlock();
}
return instance;
}
public Script getScript(String aScriptFileName) throws ScriptException, IOException {
Map<String, String> props = new HashMap<>();
return getScript(aScriptFileName, props, null);
}
private byte[] getMD5Hash(String aScriptFileName) throws FileNotFoundException, IOException {
byte[] messageDigest = null;
try (FileInputStream fis = new FileInputStream(aScriptFileName);
DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fis), MessageDigest.getInstance("MD5"))) {
byte[] bytes = new byte[8192];
while (dis.read(bytes) != -1) {
// do nothing...just computing the md5 hash
}
messageDigest = dis.getMessageDigest().digest();
} catch (NoSuchAlgorithmException swallow) {
// MD5 is a legitimate format
}
return messageDigest;
}
private String getScriptText(File scriptFile, String extension) throws IOException {
final String script;
switch (extension) {
case "rb":
script = JRubyScriptFactory.INSTANCE.getScript(scriptFile);
break;
case "js":
script = JavaScriptScriptFactory.INSTANCE.getScript(scriptFile);
break;
case "py":
script = JythonScriptFactory.INSTANCE.getScript(scriptFile);
break;
default:
script = FileUtils.readFileToString(scriptFile);
}
return script;
}
private Script getScriptInstance(final Map<String, String> properties) throws ScriptException {
Map<String, Object> localThreadVariables = new HashMap<>();
final String extension = getExtension(scriptFileName);
String loggerVariableKey = getVariableName("GLOBAL", "logger", extension);
localThreadVariables.put(loggerVariableKey, logger);
String propertiesVariableKey = getVariableName("INSTANCE", "properties", extension);
localThreadVariables.put(propertiesVariableKey, properties);
localThreadVariables.put(ScriptEngine.FILENAME, scriptFileName);
final Bindings bindings = new SimpleBindings(localThreadVariables);
final ScriptEngine scriptEngine = engineFactory.getEngine(extension);
Script instance;
if (compiledScript == null) {
instance = (Script) scriptEngine.eval(scriptText, bindings);
if (instance == null) { // which it will be for python and also for local variables in javascript
instance = (Script) scriptEngine.eval("instance", bindings);
}
} else {
instance = (Script) compiledScript.eval(bindings);
if (instance == null) { // which it will be for python and also for local variables in javascript
instance = (Script) compiledScript.getEngine().eval("instance", bindings);
}
}
instance.setEngine(scriptEngine);
return instance;
}
/*
* Must have writeLock when calling this!!!!
*/
private void updateEngine() throws IOException, ScriptException {
final String extension = getExtension(scriptFileName);
// if engine is thread safe, it's being reused...if it's a JrubyEngine it
File scriptFile = new File(this.scriptFileName);
ScriptEngine scriptEngine = engineFactory.getNewEngine(scriptFile, extension);
scriptText = getScriptText(scriptFile, extension);
Map<String, Object> localThreadVariables = new HashMap<>();
String loggerVariableKey = getVariableName("GLOBAL", "logger", extension);
localThreadVariables.put(loggerVariableKey, logger);
String propertiesVariableKey = getVariableName("INSTANCE", "properties", extension);
localThreadVariables.put(propertiesVariableKey, new HashMap<String, String>());
localThreadVariables.put(ScriptEngine.FILENAME, scriptFileName);
if (scriptEngine instanceof Compilable) {
Bindings bindings = new SimpleBindings(localThreadVariables);
scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
compiledScript = ((Compilable) scriptEngine).compile(scriptText);
}
logger.debug("Updating Engine!!");
}
private String getVariableName(String scope, String variableName, String extension) {
String result;
switch (extension) {
case "rb":
switch (scope) {
case "GLOBAL":
result = '$' + variableName;
break;
case "INSTANCE":
result = '@' + variableName;
break;
default:
result = variableName;
break;
}
break;
default:
result = variableName;
break;
}
return result;
}
private String getExtension(String aScriptFileName) {
int dotPos = aScriptFileName.lastIndexOf('.');
if (dotPos < 1) {
throw new IllegalArgumentException("Script file name must have an extension");
}
final String extension = aScriptFileName.substring(dotPos + 1);
return extension;
}
}

View File

@ -1,67 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.scripting;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptException;
/**
* <p>
* Script authors should extend this class if they want to follow the
* "processCallback" paradigm for NiFi processors.
* </p>
*
* <p>
* At a minimum, scripts must implement
* <code>process(FileInputStream, FileOutputStream)</code>.
* </p>
*
* <p>
* By default, all files processed will be sent to the relationship
* <em>success</em>, unless the scriptFileName raises an exception, in which
* case the file will be sent to <em>failure</em>. Implement
* {@link #getProcessorRelationships()} and/or {@link #getRoute()} to change
* this behavior.
* </p>
*
*/
public class WriterScript extends Script {
private Object processCallback;
public WriterScript() {
}
public WriterScript(Object... callbacks) {
super(callbacks);
for (Object callback : callbacks) {
if (callback instanceof Map<?, ?>) {
processCallback = processCallback == null && ((Map<?, ?>) callback).containsKey("process") ? callback : processCallback;
}
}
}
public void process(InputStream in, OutputStream out) throws NoSuchMethodException, ScriptException {
Invocable inv = (Invocable) engine;
inv.invokeMethod(processCallback, "process", in, out);
}
}

View File

@ -1,15 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.apache.nifi.processors.script.ExecuteScript

View File

@ -1,264 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<meta charset="utf-8" />
<title>ExecuteScript</title>
<link rel="stylesheet" href="../../css/component-usage.css" type="text/css" />
</head>
<body>
<!-- Processor Documentation ================================================== -->
<h2>Description:</h2>
<p>
This processor provides the capability to execute scripts in various scripting languages, and passes into the scripts
the input stream and output stream(s) representing an incoming flow file and any created flow files. The processor is designed to be
thread safe, so multiple concurrent tasks may execute against a single script. The processor provides a framework which enables
script writers to implement 3 different types of scripts:
<ul>
ReaderScript - which enables stream-based reading of a FlowFile's content</br>
WriterScript - which enables stream-based reading and writing/modifying of a FlowFile's content</br>
ConverterScript - which enables stream-based reading a FlowFile's content and stream-based writing to newly created FlowFiles</br>
</ul>
Presently, the processor supports 3 scripting languages: Ruby, Python, and JavaScript. The processor is built on the
javax.script API which enables ScriptEngine discovery, thread management, and encapsulates much of the low level bridging-code that
enables Java to Script language integration. Thus, it is designed to be easily extended to other scripting languages. </br>
The attributes of a FlowFile and properties of the Processor are exposed to the script by either a variable in the base class or
a getter method. A script may declare new Processor Properties and different Relationships via overriding the getPropertyDescriptors
and getRelationships methods, respectively.
</p>
The processor provides some boilerplate script to aid in the creation of the three different types of scripts. For example,
the processor provides import statements for classes commonly used within a processor.
<pre>
'org.apache.nifi.components.PropertyDescriptor'
'org.apache.nifi.components.Validator'
'org.apache.nifi.processor.util.StandardValidators'
'org.apache.nifi.processor.Relationship'
'org.apache.nifi.logging.ProcessorLog'
'org.apache.nifi.scripting.ReaderScript'
'org.apache.nifi.scripting.WriterScript'
'org.apache.nifi.scripting.ConverterScript'
</pre>
The processor appends to the script's execution path the parent directory of the specified script file and a sub-directory
called 'lib', which may be useful for supporting scripts. </p>
<p>
<strong>Shared Variables</strong>
</p>
The following variables are provided as shared variables for the scripts:
<ul>
<li>logger
<ul>
<li> The processor's logger </li>
<li> Scope is GLOBAL, thus in Ruby the syntax is $logger</li>
</ul>
</li>
<li>properties
<ul>
<li> A Map of the processor's configuration properties; key and value are strings</li>
<li> Scope is INSTANCE, thus in Ruby the syntax is @properties</li>
</ul>
</li>
</ul>
<p>
<strong>Properties:</strong>
</p>
<p>
In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered
optional. If a property has a default value, it is indicated. If a property supports the use of the NiFi Expression Language
(or simply, "expression language"), that is also indicated. Of particular note: This processor allows scripts to define additional
Processor properties, which will not be initially visible. Once the processor's configuration is validated, script defined properties
will become visible, and may affect the validity of the processor.
</p>
<ul>
<li>
<strong>Script File Name</strong>
<ul>
<li>Script location, can be relative or absolute path.</li>
<li>Default value: no default</li>
<li>Supports expression language: false</li>
</ul>
</li>
<li>
<strong>Script Check Interval</strong>
<ul>
<li>The time period between checking for updates to a script.</li>
<li>Default value: 15 sec</li>
<li>Supports expression language: false</li>
</ul>
</li>
</ul>
<p>
<strong>Relationships:</strong>
</p>
<p>
The initial 'out of the box' relationships are below. Of particular note is the ability of a script to change the set of
relationships. However, any relationships defined by the script will not be visible until the processor's configuration has been
validated. Once done, new relationships will become visible.
</p>
<ul>
<li>
success
<ul>
<li>Used when a file is successfully processed by a script.</li>
</ul>
</li>
<li>
failure
<ul>
<li>Used when an error occurs while processing a file with a script.</li>
</ul>
</li>
</ul>
<p>
<strong>Example Scripts:</strong>
</p>
<ul>
JavaScript example - the 'with' statement imports packages defined in the framework and limits the importing to the local scope,
rather than global. The 'Scripting' variable uses the JavaImporter class within JavaScript. Since the 'instance' variable is intended to
be local scope (not global), it must be named 'instance' as it it not passed back to the processor upon script evaluation and must be
fetched. If you make it global, you can name it whatever you'd like...but this is intended to be multi-threaded so do so at your own
risk.</p>
Presently, there are issues with the JavaScript scripting engine that prevent sub-classing the base classes in the Processor's Java
framework. So, what is actually happening is an instance of the ReaderScript is created with a provided callback object. When we are able
to move to a more competent scripting engine (supposedly in Java 8), the code below will remain the same, but the 'instance' variable
will actually be a sub-class of ReaderScript.
<pre>
with (Scripting) {
var instance = new ReaderScript({
route : function(input) {
var str = IOUtils.toString(input);
var expr = instance.getProperty("expr");
filename = instance.attributes.get("filename");
instance.setAttribute("filename", filename + ".modified");
if (str.match(expr)) {
return Script.FAIL_RELATIONSHIP;
} else {
return Script.SUCCESS_RELATIONSHIP;
}
}
});
}
</pre>
Ruby example - the 'OutputStreamHandler' is an interface which is called when creating flow files.
<pre>
java_import 'org.apache.nifi.scripting.OutputStreamHandler'
class SimpleConverter < ConverterScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :logger, :attributes
def convert(input)
in_io = input.to_io
createFlowFile("firstLine", FAIL_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
out_io = out.to_io
out_io << in_io.readline.to_java_bytes
out_io.close
logger.debug("Wrote data to failure...this message logged with logger from super class")
end)
createFlowFile("otherLines", SUCCESS_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
out_io = out.to_io
in_io.each_line { |line|
out_io << line
}
out_io.close
logger.debug("Wrote data to success...this message logged with logger from super class")
end)
in_io.close
end
end
$logger.debug("Creating SimpleConverter...this message logged with logger from shared variables")
SimpleConverter.new
</pre>
Python example - The difficulty with Python is that it does not return objects upon script evaluation, so the instance of the Script
class must be fetched by name. Thus, you must define a variable called 'instance'.
<pre>
import re
class RoutingReader(ReaderScript):
A = Relationship.Builder().name("a").description("some good stuff").build()
B = Relationship.Builder().name("b").description("some other stuff").build()
C = Relationship.Builder().name("c").description("some bad stuff").build()
def getRelationships(self):
return [self.A,self.B,self.C]
def getExceptionRoute(self):
return self.C
def route( self, input ):
logger.info("Executing route")
for line in FileUtil.wrap(input):
if re.match("^bad", line, re.IGNORECASE):
return self.B
if re.match("^sed", line):
raise RuntimeError("That's no good!")
return self.A
logger.debug("Constructing instance")
instance = RoutingReader()
</pre>
</ul>
<p>
<strong>Script API:</strong>
</p>
<ul>
<li>getAttribute(String) : String</li>
<li>getAttributes() : Map(String,String)</li>
<li>getExceptionRoute() : Relationship</li>
<li>getFileName() : String</li>
<li>getFlowFileEntryDate() : Calendar</li>
<li>getFlowFileSize() : long</li>
<li>getProperties() : Map(String, String)</li>
<li>getProperty(String) : String</li>
<li>getPropertyDescriptors() : List(PropertyDescriptor)</li>
<li>getRelationships() : Collection(Relationship)</li>
<li>getRoute() : Relationship</li>
<li>setRoute(Relationship)</li>
<li>setAttribute(String, String)</li>
<li>validate() : Collection(String)</li>
</ul>
<p>
<strong>ReaderScript API:</strong>
</p>
<ul>
<li>route(InputStream) : Relationship</li>
</ul>
<p>
<strong>WriterScript API:</strong>
</p>
<ul>
<li>process(InputStream, OutputStream)</li>
</ul>
<p>
<strong>ConverterScript API:</strong>
</p>
<ul>
<li>convert(InputStream)</li>
<li>createFlowFile(String, Relationship, OutputStreamHandler)</li>
</ul>
<p>
<strong>OutputStreamHandler API:</strong>
</p>
<ul>
<li>write(OutputStream)</li>
</ul>
</body>
</html>

View File

@ -1,939 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.script;
import org.apache.nifi.processors.script.ExecuteScript;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author unattributed
*
*/
public class TestExecuteScript {
static Logger LOG;
static {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
System.setProperty("org.slf4j.simpleLogger.showDateTime", "true");
System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.script.ExecuteScript", "trace");
System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.script.TestExecuteScript", "debug");
System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.AbstractProcessor", "debug");
LOG = LoggerFactory.getLogger(TestExecuteScript.class);
}
private TestRunner controller;
private final String multiline = "Lorem ipsum dolor sit amet,\n"
+ "consectetur adipisicing elit,\n"
+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"
+ "Ut enim ad minim veniam,\n"
+ "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n"
+ "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n"
+ "Excepteur sint occaecat cupidatat non proident,\n"
+ "sunt in culpa qui officia deserunt mollit anim id est laborum.";
/**
* Create a mock SingleProcessorController using our processor and pass data
* to it via byte array. Returns the Sink that provides access to any files
* that pass out of the processor
*/
@Before
public void setupEach() throws IOException {
controller = TestRunners.newTestRunner(ExecuteScript.class);
controller.setValidateExpressionUsage(false);
// copy all scripts to target directory and run from there. some python
// scripts create .class files that end up in src/test/resources.
FileUtils.copyDirectory(new File("src/test/resources"), new File("target/test-scripts"));
}
// Fail if the specified relationship does not contain exactly one file
// with the expected value
private void assertRelationshipContents(String expected, String relationship) {
controller.assertTransferCount(relationship, 1);
MockFlowFile ff = controller.getFlowFilesForRelationship(relationship).get(0);
ff.assertContentEquals(expected);
}
// Fail if the specified relationship does not contain specified number of files
// with the expected value
private void assertRelationshipContents(String expected, String relationship, int count) {
controller.assertTransferCount(relationship, count);
MockFlowFile ff = controller.getFlowFilesForRelationship(relationship).get(count - 1);
ff.assertContentEquals(expected);
}
// ////////////////////////////////////
// General tests
@Test(expected = IllegalArgumentException.class)
public void failOnBadName() {
LOG.info("Supplying bad script file names");
// None of these should result in actually setting the property, because they're non-existent / bad files
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "not/really.rb");
controller.assertNotValid();
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "fakey/fake.js");
controller.assertNotValid();
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "pom.xml");
controller.assertNotValid();
}
// ////////////////////////////////////
// Ruby script tests
@Test
public void testSimpleReadR() {
LOG.info("Ruby script: fail file based on reading contents");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readTest.rb");
controller.setThreadCount(2);
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
controller.getFlowFilesForRelationship("success").get(0).assertAttributeEquals("filename", "NewFileNameFromReadTest");
}
@Test
public void testParamReadR() {
LOG.info("Ruby script: Failing file based on reading contents");
Map<String, String> attrs1 = new HashMap<>();
attrs1.put("filename", "StuffIsFine.txt");
Map<String, String> attrs2 = new HashMap<>();
attrs2.put("filename", "multiline.txt");
controller.enqueue("This stuff is fine".getBytes(), attrs1);
controller.enqueue(multiline.getBytes(), attrs2);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.rb");
controller.setProperty("expr", "rehenderit");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testWriteLastLineR() {
LOG.info("Running Ruby script to output last line of file");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/writeTest.rb");
controller.run();
List<MockFlowFile> files = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, files.size());
byte[] blob = files.get(0).toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertEquals("sunt in culpa qui officia deserunt mollit anim id est laborum.", lines[0]);
}
@Test
public void testWriteOptionalParametersR() {
LOG.info("Ruby script that uses optional parameters");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/paramTest.rb");
controller.setProperty("repeat", "3");
controller.run();
List<MockFlowFile> files = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, files.size());
byte[] blob = files.get(0).toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File did not have 3 lines", 3, lines.length);
assertEquals("sunt in culpa qui officia deserunt mollit anim id est laborum.", lines[0]);
}
@Test
public void testSetupOptionalValidationR() {
LOG.info("Ruby script creating validators for optional properties");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/optionalValidators.rb");
controller.assertNotValid();
controller.setProperty("int", "abc");
controller.assertNotValid();
controller.setProperty("url", "not@valid");
controller.assertNotValid();
controller.setProperty("nonEmpty", "");
controller.assertNotValid();
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
controller.assertValid();
}
@Test
public void testTwoScriptsSameThreadSameClassName() {
LOG.info("Test 2 different scripts with the same ruby class name");
Map<String, String> attrs1 = new HashMap<>();
attrs1.put("filename", "StuffIsFine.txt");
Map<String, String> attrs2 = new HashMap<>();
attrs2.put("filename", "multiline.txt");
controller.enqueue("This stuff is fine".getBytes(), attrs1);
controller.enqueue(multiline.getBytes(), attrs2);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.rb");
controller.setProperty("expr", "rehenderit");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/optionalValidators.rb");
controller.assertNotValid();
controller.setProperty("int", "abc");
controller.assertNotValid();
controller.setProperty("url", "not@valid");
controller.assertNotValid();
controller.setProperty("nonEmpty", "");
controller.assertNotValid();
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
controller.assertValid();
}
@Test
public void testUpdateScriptR() throws Exception {
LOG.info("Test one script with updated class");
File testFile = File.createTempFile("script", ".rb");
File original = new File("target/test-scripts/readWithParams.rb");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, testFile.getPath());
controller.assertValid();
original = new File("target/test-scripts/optionalValidators.rb");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_CHECK_INTERVAL, "5 secs");
Thread.sleep(6000);
controller.assertNotValid();
controller.setProperty("int", "abc");
controller.assertNotValid();
controller.setProperty("url", "not@valid");
controller.assertNotValid();
controller.setProperty("nonEmpty", "");
controller.assertNotValid();
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
controller.assertValid();
FileUtils.deleteQuietly(testFile);
}
@Test
public void testMultiThreadExecR() {
LOG.info("Ruby script 20 threads: Failing file based on reading contents");
Map<String, String> attrs1 = new HashMap<>();
attrs1.put("filename", "StuffIsFine.txt");
Map<String, String> attrs2 = new HashMap<>();
attrs2.put("filename", "multiline.txt");
controller.setThreadCount(20);
for (int i = 0; i < 10; i++) {
controller.enqueue("This stuff is fine".getBytes(), attrs1);
controller.enqueue(multiline.getBytes(), attrs2);
}
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.rb");
controller.setProperty("expr", "rehenderit");
controller.run(20);
controller.assertTransferCount("failure", 10);
controller.assertTransferCount("success", 10);
for (int i = 0; i < 10; i++) {
MockFlowFile ff = controller.getFlowFilesForRelationship("failure").get(i);
ff.assertContentEquals(multiline);
assertTrue(ff.getAttribute("filename").endsWith("modified"));
ff = controller.getFlowFilesForRelationship("success").get(i);
ff.assertContentEquals("This stuff is fine");
assertTrue(ff.getAttribute("filename").endsWith("modified"));
}
}
@Test
public void testManualValidationR() {
LOG.info("Ruby script defining manual validator");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/alwaysFail.rb");
controller.assertNotValid();
}
@Test
public void testGetRelationshipsR() {
LOG.info("Ruby script: getRelationships");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/routeTest.rb");
// at this point, the script has not been instantiated so the processor simply returns an empty set
Set<Relationship> rels = controller.getProcessor().getRelationships();
assertEquals(0, rels.size());
// this will instantiate the script
controller.assertValid();
// this will call the script
rels = controller.getProcessor().getRelationships();
assertEquals(3, rels.size());
}
@Test
public void testGetExceptionRouteR() {
LOG.info("Ruby script defining route taken in event of exception");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue("Bad things go to 'b'.".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/routeTest.rb");
controller.run(3);
assertRelationshipContents("This stuff is fine", "a");
assertRelationshipContents("Bad things go to 'b'.", "b");
assertRelationshipContents(multiline, "c");
}
@Test
public void testSimpleConverterR() {
LOG.info("Running Ruby converter script");
for (int i = 0; i < 20; i++) {
controller.enqueue(multiline.getBytes());
}
controller.setThreadCount(20);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/simpleConverter.rb");
controller.run(20);
List<MockFlowFile> successFiles = controller.getFlowFilesForRelationship("success");
List<MockFlowFile> failFiles = controller.getFlowFilesForRelationship("failure");
assertEquals("Process did not generate 20 SUCCESS files", 20, successFiles.size());
assertEquals("Process did not generate 20 FAILURE files", 20, failFiles.size());
MockFlowFile sFile = successFiles.get(19);
MockFlowFile fFile = failFiles.get(19);
byte[] blob = fFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertEquals("Lorem ipsum dolor sit amet,", lines[0]);
blob = sFile.toByteArray();
lines = new String(blob).split("\n");
assertEquals("SUCCESS had wrong number of lines", 7, lines.length);
assertEquals("consectetur adipisicing elit,", lines[0]);
}
@Test
public void testLoadLocalR() {
LOG.info("Ruby: load another script file");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/loadLocal.rb");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testFlowFileR() {
LOG.info("Ruby: get FlowFile properties");
controller.enqueue(multiline.getBytes());
HashMap<String, String> meta = new HashMap<String, String>();
meta.put("evict", "yup");
controller.enqueue("This would be plenty long but it's also evicted.".getBytes(), meta);
controller.enqueue("This is too short".getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/ffTest.rb");
controller.run(3);
assertRelationshipContents(multiline, "success");
assertRelationshipContents("This is too short", "failure");
assertRelationshipContents("This would be plenty long but it's also evicted.", "evict");
}
// //////////////////////////////////// // JS tests
@Test
public void testSimpleReadJS() {
LOG.info("Javascript: fail file based on reading contents");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readTest.js");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testParamReadJS() {
LOG.info("Javascript: read contents and fail based on parameter");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.js");
controller.setProperty("expr", "sed do");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testWriteLastLineJS() {
LOG.info("Running Javascript to output last line of file");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/writeTest.js");
controller.run();
List<MockFlowFile> sunkFiles = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, sunkFiles.size());
MockFlowFile sunkFile = sunkFiles.iterator().next();
byte[] blob = sunkFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertEquals("sunt in culpa qui officia deserunt mollit anim id est laborum.", lines[0]);
}
@Test
public void testWriteOptionalParametersJS() {
LOG.info("Javascript processCallback that uses optional parameters");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/paramTest.js");
controller.setProperty("repeat", "3");
controller.run();
List<MockFlowFile> sunkFiles = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, sunkFiles.size());
MockFlowFile sunkFile = sunkFiles.iterator().next();
byte[] blob = sunkFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File did not have 3 lines", 3, lines.length);
assertEquals("sunt in culpa qui officia deserunt mollit anim id est laborum.", lines[0]);
}
@Test
public void testSetupOptionalValidationJS() {
LOG.info("Javascript creating validators for optional properties");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/optionalValidators.js");
controller.setProperty("int", "abc");
controller.setProperty("url", "not@valid");
controller.setProperty("nonEmpty", "");
assertEquals(2, controller.getProcessor().getPropertyDescriptors().size());
controller.assertNotValid(); // due to invalid values above
assertEquals(5, controller.getProcessor().getPropertyDescriptors().size());
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
assertEquals(5, controller.getProcessor().getPropertyDescriptors().size());
controller.assertValid();
}
@Test
public void testManualValidationJS() {
LOG.info("Javascript defining manual validator");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/alwaysFail.js");
controller.assertNotValid();
}
@Test
public void testGetExceptionRouteJS() {
LOG.info("Javascript defining route taken in event of exception");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue("Bad things go to 'b'.".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/routeTest.js");
controller.run(3);
assertRelationshipContents("This stuff is fine", "a");
assertRelationshipContents("Bad things go to 'b'.", "b");
assertRelationshipContents(multiline, "c");
}
@Test
public void testSimpleConverterJS() {
LOG.info("Running Javascript converter script");
for (int i = 0; i < 20; i++) {
controller.enqueue(multiline.getBytes());
}
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/simpleConverter.js");
controller.run(20);
List<MockFlowFile> successFiles = controller.getFlowFilesForRelationship("success");
List<MockFlowFile> failFiles = controller.getFlowFilesForRelationship("failure");
assertEquals("Process did not generate 20 SUCCESS files", 20, successFiles.size());
assertEquals("Process did not generate 20 FAILURE file", 20, failFiles.size());
MockFlowFile sFile = successFiles.get(19);
MockFlowFile fFile = failFiles.get(0);
byte[] blob = sFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("SUCCESS had wrong number of lines", 7, lines.length);
assertTrue(lines[0].startsWith("consectetur adipisicing elit,"));
blob = fFile.toByteArray();
lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertTrue(lines[0].startsWith("Lorem ipsum dolor sit amet,"));
}
@Test
public void testLoadLocalJS() {
LOG.info("Javascript: load another script file");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/loadLocal.js");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testXMLJS() {
LOG.info("Javascript: native XML parser");
controller.enqueue("<a><b foo='bar'>Bad</b><b good='true'>Good</b><b good='false'>Bad</b></a>".getBytes());
controller.enqueue("<a><b>Hello</b><b>world</b></a>".getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/parseXml.js");
controller.run(2);
assertRelationshipContents("Good", "success");
assertRelationshipContents("<a><b>Hello</b><b>world</b></a>", "failure");
}
@Test
public void testFlowFileJS() {
LOG.info("JavaScript: get FlowFile properties");
controller.enqueue("This is too short".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/ffTest.js");
controller.run(2);
assertRelationshipContents(multiline, "success");
assertRelationshipContents("This is too short", "failure");
}
@Test
public void testMultiThreadExecJS() {
LOG.info("JavaScript script 20 threads: Failing file based on reading contents");
Map<String, String> attrs1 = new HashMap<>();
attrs1.put("filename", "StuffIsFine.txt");
Map<String, String> attrs2 = new HashMap<>();
attrs2.put("filename", "multiline.txt");
controller.setThreadCount(20);
for (int i = 0; i < 10; i++) {
controller.enqueue("This stuff is fine".getBytes(), attrs1);
controller.enqueue(multiline.getBytes(), attrs2);
}
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.js");
controller.setProperty("expr", "rehenderit");
controller.run(20);
controller.assertTransferCount("failure", 10);
controller.assertTransferCount("success", 10);
for (int i = 0; i < 10; i++) {
MockFlowFile ff = controller.getFlowFilesForRelationship("failure").get(i);
ff.assertContentEquals(multiline);
assertTrue(ff.getAttribute("filename").endsWith("modified"));
ff = controller.getFlowFilesForRelationship("success").get(i);
ff.assertContentEquals("This stuff is fine");
assertTrue(ff.getAttribute("filename").endsWith("modified"));
}
}
@Test
public void testUpdateScriptJS() throws Exception {
LOG.info("Test one script with updated class");
File testFile = File.createTempFile("script", ".js");
File original = new File("target/test-scripts/readWithParams.js");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, testFile.getPath());
controller.assertValid();
original = new File("target/test-scripts/optionalValidators.js");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_CHECK_INTERVAL, "5 secs");
Thread.sleep(6000);
controller.assertNotValid();
controller.setProperty("int", "abc");
controller.assertNotValid();
controller.setProperty("url", "not@valid");
controller.assertNotValid();
controller.setProperty("nonEmpty", "");
controller.assertNotValid();
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
controller.assertValid();
FileUtils.deleteQuietly(testFile);
}
// ////////////////////////////////// // Python script tests
@Test
public void testSimpleReadP() {
LOG.info("Python script: fail file based on reading contents");
for (int i = 0; i < 20; i++) {
Map<String, String> attr1 = new HashMap<>();
attr1.put("filename", "FineStuff");
attr1.put("counter", Integer.toString(i));
Map<String, String> attr2 = new HashMap<>();
attr2.put("filename", "MultiLine");
attr2.put("counter", Integer.toString(i));
controller.enqueue("This stuff is fine".getBytes(), attr1);
controller.enqueue(multiline.getBytes(), attr2);
}
controller.setThreadCount(40);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readTest.py");
controller.run(40);
assertRelationshipContents(multiline, "failure", 20);
assertRelationshipContents("This stuff is fine", "success", 20);
List<MockFlowFile> fails = controller.getFlowFilesForRelationship("failure");
List<MockFlowFile> successes = controller.getFlowFilesForRelationship("success");
for (int i = 0; i < 20; i++) {
assertTrue(fails.get(i).getAttribute("filename").matches("^.*\\d+$"));
assertTrue(successes.get(i).getAttribute("filename").matches("^.*\\d+$"));
}
}
@Test
public void testParamReadP() {
LOG.info("Python script: read contents and fail based on parameter");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.py");
controller.setProperty("expr", "sed do");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testWriteLastLineP() {
LOG.info("Running Python script to output last line of file");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/writeTest.py");
controller.run();
List<MockFlowFile> sunkFiles = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, sunkFiles.size());
MockFlowFile sunkFile = sunkFiles.iterator().next();
byte[] blob = sunkFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertEquals("sunt in culpa qui officia deserunt mollit anim id est laborum.", lines[0]);
}
@Test
public void testWriteOptionalParametersP() {
LOG.info("Python script processCallback that uses optional parameters");
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/paramTest.py");
controller.setProperty("repeat", "3");
controller.run();
List<MockFlowFile> sunkFiles = controller.getFlowFilesForRelationship("success");
assertEquals("Process did not generate an output file", 1, sunkFiles.size());
MockFlowFile sunkFile = sunkFiles.iterator().next();
byte[] blob = sunkFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("File did not have 3 lines", 3, lines.length);
assertTrue(lines[2].startsWith("sunt in culpa qui officia deserunt mollit anim id est laborum."));
}
@Test
public void testManualValidationP() {
LOG.info("Python defining manual validator");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/alwaysFail.py");
controller.assertNotValid();
}
@Test
public void testSetupOptionalValidationP() {
LOG.info("Python script creating validators for optional properties");
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/optionalValidators.py");
controller.setProperty("int", "abc");
controller.setProperty("url", "not@valid");
controller.setProperty("nonEmpty", "");
assertEquals(2, controller.getProcessor().getPropertyDescriptors().size());
controller.assertNotValid();
controller.setProperty("int", "123");
controller.setProperty("url", "http://localhost");
controller.setProperty("nonEmpty", "abc123");
assertEquals(5, controller.getProcessor().getPropertyDescriptors().size());
controller.assertValid();
}
@Test
public void testGetExceptionRouteP() {
LOG.info("Python script defining route taken in event of exception");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue("Bad things go to 'b'.".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/routeTest.py");
// Don't put the error in the logs
// TestableAppender ta = new TestableAppender();
// ta.attach(Logger.getLogger(ExecuteScript.class));
controller.run(3);
// ta.detach();
assertRelationshipContents("This stuff is fine", "a");
assertRelationshipContents("Bad things go to 'b'.", "b");
assertRelationshipContents(multiline, "c");
// ta.assertFound("threw exception");
}
@Test
public void testLoadLocalP() throws Exception {
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
testGetExceptionRouteP();
setupEach();
} catch (Exception e) {
}
}
});
t.start();
t.join();
LOG.info("Python: load another script file");
controller.enqueue("This stuff is fine".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/loadLocal.py");
controller.run(2);
assertRelationshipContents(multiline, "failure");
assertRelationshipContents("This stuff is fine", "success");
}
@Test
public void testSimpleConverterP() {
LOG.info("Running Python converter script");
for (int i = 0; i < 20; i++) {
controller.enqueue(multiline.getBytes());
}
controller.setThreadCount(20);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/simpleConverter.py");
controller.run(20);
List<MockFlowFile> successFiles = controller.getFlowFilesForRelationship("success");
List<MockFlowFile> failFiles = controller.getFlowFilesForRelationship("failure");
assertEquals("Process did not generate 20 SUCCESS files", 20, successFiles.size());
assertEquals("Process did not generate 20 FAILURE files", 20, failFiles.size());
MockFlowFile sFile = successFiles.iterator().next();
MockFlowFile fFile = failFiles.iterator().next();
byte[] blob = sFile.toByteArray();
String[] lines = new String(blob).split("\n");
assertEquals("SUCCESS had wrong number of lines", 7, lines.length);
assertTrue(lines[0].startsWith("consectetur adipisicing elit,"));
blob = fFile.toByteArray();
lines = new String(blob).split("\n");
assertEquals("File had more than one line", 1, lines.length);
assertTrue(lines[0].startsWith("Lorem ipsum dolor sit amet,"));
}
@Test
public void testFlowFileP() {
LOG.info("Python: get FlowFile properties");
controller.enqueue("This is too short".getBytes());
controller.enqueue(multiline.getBytes());
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/ffTest.py");
controller.run(2);
assertRelationshipContents(multiline, "success");
assertRelationshipContents("This is too short", "failure");
}
@Test
public void testMultiThreadExecP() {
LOG.info("Pthon script 20 threads: Failing file based on reading contents");
Map<String, String> attrs1 = new HashMap<>();
attrs1.put("filename", "StuffIsFine.txt");
Map<String, String> attrs2 = new HashMap<>();
attrs2.put("filename", "multiline.txt");
for (int i = 0; i < 10; i++) {
controller.enqueue("This stuff is fine".getBytes(), attrs1);
controller.enqueue(multiline.getBytes(), attrs2);
}
controller.setThreadCount(20);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, "target/test-scripts/readWithParams.py");
controller.setProperty("expr", "sed do");
controller.run(20);
controller.assertTransferCount("failure", 10);
controller.assertTransferCount("success", 10);
for (int i = 0; i < 10; i++) {
MockFlowFile ff = controller.getFlowFilesForRelationship("failure").get(i);
ff.assertContentEquals(multiline);
assertTrue(ff.getAttribute("filename").endsWith("modified"));
ff = controller.getFlowFilesForRelationship("success").get(i);
ff.assertContentEquals("This stuff is fine");
assertTrue(ff.getAttribute("filename").endsWith("modified"));
}
}
@Test
public void testUpdateScriptP() throws Exception {
LOG.info("Test one script with updated class");
File testFile = File.createTempFile("script", ".py");
File original = new File("target/test-scripts/readTest.py");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_FILE_NAME, testFile.getPath());
controller.assertValid();
original = new File("target/test-scripts/readWithParams.py");
FileUtils.copyFile(original, testFile);
controller.setProperty(ExecuteScript.SCRIPT_CHECK_INTERVAL, "5 secs");
Thread.sleep(6000);
controller.assertNotValid(); // need to set 'expr'
controller.setProperty("int", "abc");
controller.assertNotValid();
controller.setProperty("url", "not@valid");
controller.assertNotValid();
controller.setProperty("nonEmpty", "");
controller.assertNotValid();
controller.setProperty("expr", "sed do");
controller.assertValid();
assertEquals(6, controller.getProcessContext().getProperties().size());
FileUtils.deleteQuietly(testFile);
}
}

View File

@ -1,24 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new ReaderScript({
validate: function () {
return ["This will never work."];
}
});
}

View File

@ -1,19 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# type(name, superclass_tuple, member_dict) is a shorthand for defining an
# anonymous class. Note the trailing parens (), because scriptBuilder must
# return an *instance* of the class.
instance = type("FailingReader", (ReaderScript, object),\
{"validate": lambda self : ["I am broken"]})()

View File

@ -1,21 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class NeverSatisfied < ReaderScript
def validate
return ["This is supposed to fail"]
end
end
NeverSatisfied.new

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new ReaderScript({
route: function (input) {
if (instance.getFlowFileSize() < 20) {
return Script.FAIL_RELATIONSHIP;
} else {
return Script.SUCCESS_RELATIONSHIP;
}
}
});
}

View File

@ -1,22 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJythonReader(ReaderScript):
def route(self, input):
if self.getFlowFileSize() < 20 : return self.FAIL_RELATIONSHIP
return self.SUCCESS_RELATIONSHIP
instance = SimpleJythonReader()

View File

@ -1,30 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyReader < ReaderScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP
def getRelationships
@@evict = Relationship::Builder.new.name("evict").description("some evicted stuff").build()
[FAIL_RELATIONSHIP, SUCCESS_RELATIONSHIP, @@evict]
end
def route( input )
return FAIL_RELATIONSHIP if getFlowFileSize < 20
return @@evict if !getAttribute("evict").nil?
return SUCCESS_RELATIONSHIP
end
end
SimpleJRubyReader.new

View File

@ -1,18 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class TestFilter:
def notAllowed(self):
return "^sed"

View File

@ -1,22 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function notAllowed() { // Works for eval(readFile(...))
return /sed do/i;
}
exports.notAllowed = notAllowed; // Works for require(...)

View File

@ -1,17 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
def notAllowed
return /^sed/i
end

View File

@ -1,30 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var sub = require("lib/sub.js");
with (Scripting) {
var instance = new ReaderScript({
route: function (input) {
var str = IOUtils.toString(input);
if (str.match(sub.notAllowed())) {
return Script.FAIL_RELATIONSHIP;
} else {
return Script.SUCCESS_RELATIONSHIP;
}
}
});
}

View File

@ -1,26 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from Sub import TestFilter
class SimpleJythonReader(ReaderScript):
def route(self, input):
tf = TestFilter()
for line in FileUtil.wrap(input):
if re.match(tf.notAllowed(),line): return self.FAIL_RELATIONSHIP
return self.SUCCESS_RELATIONSHIP
instance = SimpleJythonReader()

View File

@ -1,29 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'sub'
class SimpleJRubyReader < ReaderScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP
def route( input )
input.to_io.each_line do |line|
return FAIL_RELATIONSHIP if line.match notAllowed
end
return SUCCESS_RELATIONSHIP
end
end
SimpleJRubyReader.new

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<param name="Threshold" value="DEBUG"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{MM-dd-yy HH:mm:ss.SSS} %-5p %c{2} %x - %m%n"/>
</layout>
</appender>
<logger name="org.nifi.model.processor.FlowFileProcessor" additivity="false">
<level value="INFO"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="junit.TestableAppender" additivity="false">
<level value="TRACE"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.nifi.model.processor.impl.ScriptRunnerProcessor" additivity="false">
<level value="WARN"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.nifi.model.processor.impl.ScriptRunnerProcessorTest" additivity="false">
<level value="INFO"/>
<appender-ref ref="CONSOLE"/>
</logger>
<root>
<level value="INFO"/>
<appender-ref ref="CONSOLE" />
</root>
</log4j:configuration>

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new WriterScript({
getPropertyDescriptors: function () {
i = new PropertyDescriptor.Builder().name("int").description("an int").required(true).addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR).build();
u = new PropertyDescriptor.Builder().name("url").description("a url").required(true).addValidator(StandardValidators.URL_VALIDATOR).build();
s = new PropertyDescriptor.Builder().name("nonEmpty").description("a non empty property").required(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
return [i, u, s];
}
});
}

View File

@ -1,22 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJythonReader(ReaderScript):
def getPropertyDescriptors( self ):
nev = PropertyDescriptor.Builder().name("nonEmpty").required(1).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build()
iv = PropertyDescriptor.Builder().name("int").required(1).addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR).build()
uv = PropertyDescriptor.Builder().name("url").required(1).addValidator(StandardValidators.URL_VALIDATOR).build()
return [nev, iv, uv]
instance = SimpleJythonReader()

View File

@ -1,39 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyReader < ReaderScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :logger
def getPropertyDescriptors
logger.debug("Defining descriptors");
i = StandardValidators::INTEGER_VALIDATOR
u = StandardValidators::URL_VALIDATOR
s = StandardValidators::NON_EMPTY_VALIDATOR
intPropDesc = PropertyDescriptor::Builder.new().name("int").required(true).addValidator(i).build()
urlPropDesc = PropertyDescriptor::Builder.new().name("url").required(true).addValidator(u).build()
nonEmptyPropDesc = PropertyDescriptor::Builder.new().name("nonEmpty").addValidator(s).build()
return [intPropDesc, urlPropDesc, nonEmptyPropDesc]
end
def route( input )
logger.debug("Routing input");
input.to_io.each_line do |line|
return FAIL_RELATIONSHIP if line.match /^sed/i
end
return SUCCESS_RELATIONSHIP
end
end
$logger.debug("Creating SimpleJRubyReader with props" + @properties.to_s)
SimpleJRubyReader.new

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new WriterScript({
process: function (input, output) {
var str = IOUtils.toString(input);
var last = str.split("\n").pop() + "\n";
for (var i = 0; i < instance.getProperty("repeat"); i++) {
IOUtils.write(last, output);
}
output.flush();
}
});
}

View File

@ -1,26 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class ParamUsingWriter(WriterScript):
def process ( self, input, output ):
last = FileUtil.wrap(input).readlines()[-1] + '\n'
writer = FileUtil.wrap(output)
times = int(self.getProperty("repeat"))
lines = [last] * times
writer.writelines(lines)
writer.close()
instance = ParamUsingWriter()

View File

@ -1,31 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class ParamUsingWriter < WriterScript
def process ( input, output )
reader = input.to_io
writer = output.to_io
last = reader.readlines.last
getProperty("repeat").to_i.times do
writer << last + "\n"
end
writer.close
reader.close
end
end
ParamUsingWriter.new

View File

@ -1,36 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new WriterScript({
process: function (istream, ostream) {
var str = IOUtils.toString(istream);
var obj = new XML(str);
print(obj)
var good = obj.b.(@good == "true");
if (good.length() == 0) {
instance.setRoute(Script.FAIL_RELATIONSHIP);
IOUtils.write(str, ostream);
} else {
instance.setRoute(Script.SUCCESS_RELATIONSHIP);
for each (var goodStr in good) {
IOUtils.write(goodStr, ostream);
}
}
ostream.flush();
}
});
}

View File

@ -1,30 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new ReaderScript({
route: function (input) {
str = IOUtils.toString(input);
if (str.match(/sed do/i)) {
return Script.FAIL_RELATIONSHIP;
} else {
return Script.SUCCESS_RELATIONSHIP;
}
}
});
logger.debug("Got a logger and properties" + properties);
}

View File

@ -1,32 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os, re, subprocess
class SimpleJythonReader(ReaderScript):
def route(self, input):
logger.info("In route")
returnid = os.system("c:\\cygwin\\bin\\echo GOOD")
fname = self.getAttribute("filename")
counter = self.getAttribute("counter")
fname = fname + '.' + counter
self.setAttribute("filename", fname)
for line in FileUtil.wrap(input):
if re.match("^sed",line): return self.FAIL_RELATIONSHIP
return self.SUCCESS_RELATIONSHIP
instance = SimpleJythonReader()

View File

@ -1,30 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyReader < ReaderScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :logger, :attributes
def route( input )
logger.info("Route Input")
input.to_io.each_line do |line|
return FAIL_RELATIONSHIP if line.match /^sed/i
end
attributes.put("filename", "NewFileNameFromReadTest")
return SUCCESS_RELATIONSHIP
end
end
$logger.info("Logger is made available in shared variables...however, the SimpleJRubyReader.logger is not set till after this script returns")
SimpleJRubyReader.new

View File

@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new ReaderScript({
route: function (input) {
var str = IOUtils.toString(input);
var expr = instance.getProperty("expr");
filename = instance.attributes.get("filename");
instance.setAttribute("filename", filename + ".modified");
if (str.match(expr)) {
return Script.FAIL_RELATIONSHIP;
} else {
return Script.SUCCESS_RELATIONSHIP;
}
}
});
}

View File

@ -1,32 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class SimpleJythonReader(ReaderScript):
def getPropertyDescriptors( self ):
nev = StandardValidators.NON_EMPTY_VALIDATOR
return [PropertyDescriptor.Builder().name("expr").required(1).addValidator(nev).build()]
def route( self, input ):
expr = self.getProperty("expr")
filename = self.getAttribute("filename")
self.setAttribute("filename", filename + ".modified")
for line in FileUtil.wrap(input):
if re.match(expr, line): return self.FAIL_RELATIONSHIP
return self.SUCCESS_RELATIONSHIP
instance = SimpleJythonReader()

View File

@ -1,33 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyReader < ReaderScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :properties, :attributes
def route( input )
expr = properties.get "expr"
raise "Must specify the 'expr' property!" if expr.nil?
filename = attributes.get "filename"
setAttribute("filename", filename + ".modified")
input.to_io.each_line do |line|
return FAIL_RELATIONSHIP if line.match expr
end
return SUCCESS_RELATIONSHIP
end
end
$logger.debug("Can access logger and properties via shared instance variables...props = " + @properties.to_s)
SimpleJRubyReader.new

View File

@ -1,41 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var a = new Relationship.Builder().name("a").description("some good stuff").build()
var b = new Relationship.Builder().name("b").description("some other stuff").build()
var c = new Relationship.Builder().name("c").description("some bad stuff").build()
var instance = new ReaderScript({
getExceptionRoute: function () {
return c;
},
getRelationships: function () {
return [a, b, c];
},
route: function (input) {
var str = IOUtils.toString(input);
var lines = str.split("\n");
for (var line in lines) {
if (lines[line].match(/^bad/i)) {
return b;
} else if (lines[line].match(/^sed/i)) {
throw "That's no good!";
}
}
return a;
}
});
}

View File

@ -1,37 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class RoutingReader(ReaderScript):
A = Relationship.Builder().name("a").description("some good stuff").build()
B = Relationship.Builder().name("b").description("some other stuff").build()
C = Relationship.Builder().name("c").description("some bad stuff").build()
def getRelationships(self):
return [self.A,self.B,self.C]
def getExceptionRoute(self):
return self.C
def route( self, input ):
for line in FileUtil.wrap(input):
if re.match("^bad", line, re.IGNORECASE):
return self.B
if re.match("^sed", line):
raise RuntimeError("That's no good!")
return self.A
instance = RoutingReader()

View File

@ -1,39 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyReader < ReaderScript
@@a = Relationship::Builder.new().name("a").description("some good stuff").build()
@@b = Relationship::Builder.new().name("b").description("some bad stuff").build()
@@c = Relationship::Builder.new().name("c").description("some other stuff").build()
def getRelationships
return [@@a, @@b, @@c]
end
def getExceptionRoute
@@c
end
def route( input )
input.to_io.each_line do |line|
return @@b if line.match /^bad/i
raise "That's no good!" if line.match /^sed/i
end
@@a
end
end
SimpleJRubyReader.new

View File

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new ConverterScript({
convert: function (input) {
var buffReader = new java.io.BufferedReader(new java.io.InputStreamReader(input));
instance.createFlowFile("firstLine", Script.FAIL_RELATIONSHIP, function (output) {
var out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(output));
var firstLine = buffReader.readLine();
out.write(firstLine, 0, firstLine.length());
out.flush();
out.close();
});
instance.createFlowFile("otherLines", Script.SUCCESS_RELATIONSHIP, function (output) {
var out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(output));
var line = buffReader.readLine();
while (line != null) {
out.write(line, 0, line.length());
out.newLine();
line = buffReader.readLine();
}
out.flush();
out.close();
});
}
});
logger.debug("Processor props" + properties)
}

View File

@ -1,60 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from org.python.core.io import TextIOWrapper,BufferedReader,BufferedWriter,StreamIO
from org.apache.nifi.scripting import OutputStreamHandler
class WriteFirstLine(OutputStreamHandler):
def __init__(self, wrappedIn):
self.wrappedIn = wrappedIn
def write(self, output):
streamOut = StreamIO(output, False)
bufWrtr = BufferedWriter(streamOut, 8192)
wrappedOut = TextIOWrapper(bufWrtr)
wrappedOut.write(self.wrappedIn.readline(8192))
wrappedOut.flush()
wrappedOut.close()
class WriteOtherLines(OutputStreamHandler):
def __init__(self, wrappedIn):
self.wrappedIn = wrappedIn
def write(self, output):
streamOut = StreamIO(output, False)
bufWrtr = BufferedWriter(streamOut, 8192)
wrappedOut = TextIOWrapper(bufWrtr)
line = self.wrappedIn.readline(8192)
while line != '':
wrappedOut.write(line)
line = self.wrappedIn.readline(8192)
wrappedOut.flush()
wrappedOut.close()
class SimpleConverter(ConverterScript):
def convert(self, input):
streamIn = StreamIO(input, False)
bufRdr = BufferedReader(streamIn, 8192)
wrappedIn = TextIOWrapper(bufRdr)
writeFirstLine = WriteFirstLine(wrappedIn)
self.createFlowFile("firstLine", self.FAIL_RELATIONSHIP, writeFirstLine)
writeOtherLines = WriteOtherLines(wrappedIn)
self.createFlowFile("otherLines", self.SUCCESS_RELATIONSHIP, writeOtherLines)
instance = SimpleConverter()

View File

@ -1,42 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
java_import 'org.apache.nifi.scripting.OutputStreamHandler'
class SimpleConverter < ConverterScript
field_reader :FAIL_RELATIONSHIP, :SUCCESS_RELATIONSHIP, :logger, :attributes
def convert(input)
in_io = input.to_io
createFlowFile("firstLine", FAIL_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
out_io = out.to_io
out_io << in_io.readline.to_java_bytes
out_io.close
logger.debug("Wrote data to failure...this message logged with logger from super class")
end)
createFlowFile("otherLines", SUCCESS_RELATIONSHIP, OutputStreamHandler.impl do |method, out|
out_io = out.to_io
in_io.each_line { |line|
out_io << line
}
out_io.close
logger.debug("Wrote data to success...this message logged with logger from super class")
end)
in_io.close
end
end
$logger.debug("Creating SimpleConverter...this message logged with logger from shared variables")
SimpleConverter.new

View File

@ -1,26 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
with (Scripting) {
var instance = new WriterScript({
process: function (input, output) {
var str = IOUtils.toString(input);
IOUtils.write(str.split("\n").pop(), output);
output.flush();
}
});
}

View File

@ -1,22 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleWriter(WriterScript):
def process( self, input, output ):
last = FileUtil.wrap(input).readlines()[-1]
writer = FileUtil.wrap(output)
writer.write(last)
writer.close()
instance = SimpleWriter()

View File

@ -1,32 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class SimpleJRubyRunner < WriterScript
def process( input, output )
in_io = input.to_io
out_io = output.to_io
last = nil
in_io.each_line do |line|
last = line
end
out_io << last unless last.nil?
in_io.close
out_io.close
end
end
SimpleJRubyRunner.new

View File

@ -1,36 +0,0 @@
<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/maven-v4_0_0.xsd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>execute-script-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>execute-script-nar</artifactId>
<name>Execute Script NAR</name>
<packaging>nar</packaging>
<description>NiFi Script Running NAR</description>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>execute-script-processors</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,81 +0,0 @@
<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">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nar-container-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>execute-script-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Execute Script Bundle</name>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-stream-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-core-flowfile-attributes</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby</artifactId>
<version>1.7.16.1</version>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7-b3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>execute-script-processors</module>
<module>nar</module>
</modules>
</project>

View File

@ -1,76 +0,0 @@
<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">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>kafka-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>kafka-processors</artifactId>
<packaging>jar</packaging>
<name>kafka-processors</name>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.8.2</artifactId>
<version>0.8.1</version>
<exclusions>
<!-- Transitive dependencies excluded because they are located
in a legacy Maven repository, which Maven 3 doesn't support. -->
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,35 +0,0 @@
<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">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nar-bundle-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>kafka-bundle</artifactId>
<packaging>pom</packaging>
<name>kafka-bundle</name>
<modules>
<module>kafka-processors</module>
<module>kafka-nar</module>
</modules>
</project>

View File

@ -1,39 +0,0 @@
<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/maven-v4_0_0.xsd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>monitor-threshold-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>monitor-threshold-nar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MonitorThreshold-NAR</name>
<packaging>nar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>monitor-threshold-processor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>monitor-threshold-ui</artifactId>
<type>war</type>
</dependency>
</dependencies>
</project>

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>monitor-threshold-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>monitor-threshold-processor</artifactId>
<name>MonitorThreshold-Processor</name>
<description>Processor that counts files with particular attribute values and notifies when a count exceeds a user specified threshold</description>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,904 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.monitor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.util.file.FileUtils;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.logging.ProcessorLog;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.util.Tuple;
/**
* This processor has an advanced user interface that is used to specify
* attributes that should be monitored, as well as thresholds for individual
* values that are expected to appear in the monitored attributes.
*/
/* Because this processor maintains counts (in memory) that must be persisted
* to disk according to a prescribed schedule (See OPT_COUNT_RESET_TIME/OPT_COUNT_RESET_MINUTES
* properties), this processor must be timer driven. This processor cannot be event driven.
* Using @TriggerWhenEmpty ensures that the processor will be timer driven, and that its
* onTrigger method will be called even when no Flowfiles are in an incoming queue.
*/
@TriggerWhenEmpty
@Tags({"monitor", "threshold", "throttle", "limit", "counts", "size", "bytes", "files", "attribute", "values", "notify"})
@CapabilityDescription("Examines values found in selected FlowFile attributes and "
+ "maintains counts of the number of times the values have been encountered."
+ "The counts are then used to check against user defined thresholds.")
public class MonitorThreshold extends AbstractProcessor {
// default property values
public static final String DEFAULT_THRESHOLDS_KEY = "Default";
public static final String DEFAULT_COUNTS_PERSISTENCE_FILE_PREFIX = "conf/MonitorThreshold";
public static final int DEFAULT_SAVE_COUNTS_FREQ_SECS = 30;
public static final String DEFAULT_DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES = ".";
public static final String DEFAULT_ADD_ATRIBUTES_WHEN_THRESHOLD_EXCEEDED = "true";
public static final String ALWAYS = "Always";
public static final String ONLY_WHEN_THRESHOLD_EXCEEDED = "Only When Threshold Exceeded";
public static final String NEVER = "Never";
public static final String DEFAULT_ADD_ATRIBUTES = ONLY_WHEN_THRESHOLD_EXCEEDED;
// constants/variables, used for clearing/persisting counts
public static final long MILLIS_IN_MINUTE = 60000L;
public static final long MILLIS_IN_HOUR = MILLIS_IN_MINUTE * 60;
public static final String TIME_REGEX = "^((?:[0-1]?[0-9])|(?:[2][0-3])):([0-5]?[0-9]):([0-5]?[0-9])";
public static final Pattern TIME_PATTERN = Pattern.compile(TIME_REGEX);
private Long timeCountsLastCleared = System.currentTimeMillis();
private Long timeCountsLastSaved = null;
// other constants
public static final String NUM_APPLICABLE_THRESHOLDS_KEY = "numApplicableThresholds";
public static final String NUM_THRESHOLDS_EXCEEDED_KEY = "numThresholdsExceeded";
public static final String FILE_COUNT_PREFIX = "fileCount";
public static final String FILE_THRESHOLD_PREFIX = "fileThreshold";
public static final String BYTE_COUNT_PREFIX = "byteCount";
public static final String BYTE_THRESHOLD_PREFIX = "byteThreshold";
public static final String AGGREGATE_COUNTS_WHEN_NO_THRESHOLD_PROVIDED_PROPERTY = "Aggregate Counts When No Threshold Provided";
public static final String COUNT_RESET_TIME_PROPERTY = "Count Reset Time";
public static final String MINUTES_TO_WAIT_BEFORE_RESETTING_COUNTS_PROPERTY = "Minutes to Wait Before Resetting Counts";
public static final String ADD_ATTRIBUTES_PROPERTY = "Add Attributes";
public static final String MAX_ATTRIBUTE_PAIRS_TO_ADD_PROPERTY = "Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded";
public static final String ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES_PROPERTY = "Attribute to use for Counting Bytes";
public static final String DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES_PROPERTY = "Delimiter to use for Counts Attributes";
public static final String STANDARD_FLOWFILE_FILESIZE_PROPERTY = "fileSize";
public static final String FREQUENCY_TO_SAVE_COUNTS_PROPERTY = "Frequency to Save Counts (seconds)";
public static final String PREFIX_FOR_COUNTS_PERSISTENCE_FILE_PROPERTY = "Prefix for Counts Persistence File";
// locks for concurrency protection
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// file and byte counts, etc...
private ConcurrentHashMap<String, ConcurrentHashMap<String, CompareRecord>> counts = null; // Guarded by rwLock
private Map<String, Map<String, CompareRecord>> thresholds; // Guarded by rwLock
// optional properties
private List<PropertyDescriptor> properties;
public static final PropertyDescriptor OPT_AGGREGATE_COUNTS_WHEN_NO_THRESHOLD_PROVIDED = new PropertyDescriptor.Builder()
.name(AGGREGATE_COUNTS_WHEN_NO_THRESHOLD_PROVIDED_PROPERTY)
.description("When a value (in a monitored attribute) is encountered, but no thresholds have been specified "
+ "for that value, how should counts be maintained? "
+ "If this property is true (default), two behaviors are adopted. "
+ "First, counts for ALL non-threshold values are aggregated into a single count. "
+ "Second, when a non-threshold value is encountered, the aggregate count will be compared to the default threshold."
+ "If this property is false, the behaviors change, as follows. "
+ "Separate, individual counts are maintained for each non-threshold, value that is encountered. "
+ "When a non-threshold value is encountered, it's unique count will be compared with the default threshold. "
+ "NOTE: Counts are never maintained for non-monitored attributes. ")
.required(false)
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
.defaultValue("true")
.allowableValues("true", "false")
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_COUNT_RESET_TIME = new PropertyDescriptor.Builder()
.name(COUNT_RESET_TIME_PROPERTY)
.description("The time (24-hour clock, hh:mm:ss format, GMT Timezone) when the byte and file counts will be reset to zero. "
+ "Note: To prevent counts from increasing forever, you must specify either a '" + COUNT_RESET_TIME_PROPERTY + "' or '" + MINUTES_TO_WAIT_BEFORE_RESETTING_COUNTS_PROPERTY + "'")
.required(false)
.addValidator(StandardValidators.createRegexMatchingValidator(TIME_PATTERN))
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_COUNT_RESET_MINUTES = new PropertyDescriptor.Builder()
.name(MINUTES_TO_WAIT_BEFORE_RESETTING_COUNTS_PROPERTY)
.description("Minutes to delay count reset, beginning at '" + COUNT_RESET_TIME_PROPERTY + "', if provided. "
+ "If '" + COUNT_RESET_TIME_PROPERTY + "' is not provided, then the minutes to delay are counted from the last reset. "
+ "(Last reset is initially the time when the processor is created (added to a flow).)")
.required(false)
.addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_ADD_ATTRIBUTES = new PropertyDescriptor.Builder()
.name(ADD_ATTRIBUTES_PROPERTY)
.description("Setting this property to'" + ONLY_WHEN_THRESHOLD_EXCEEDED + "' will cause two additional attributes to be added to"
+ "FlowFiles for every threshold that is exceeded. For example, if a file count threshold is exceeded, "
+ " a fileCount.attributeName.value and a fileThreshold.attributeName.value will be added to the FlowFile. "
+ "Setting this property to'" + ALWAYS + "' will cause attributes to be added, no matter whether a threshold is exceeded."
+ "Note 1: Attributes are only added if a monitored attribute is present on the FlowFile, no matter if '" + "' " + ADD_ATTRIBUTES_PROPERTY + "' is set to '" + ALWAYS + "'. "
+ "Note 2: This processor makes a log entry for every threshold that is exceeded, no matter the value of this property. ")
.required(false)
.defaultValue(DEFAULT_ADD_ATRIBUTES)
.allowableValues(ALWAYS, ONLY_WHEN_THRESHOLD_EXCEEDED, NEVER)
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_MAX_ATTRIBUTE_PAIRS_TO_ADD_WHEN_MULTIPLE_THRESHOLD_EXCEEDED = new PropertyDescriptor.Builder()
.name(MAX_ATTRIBUTE_PAIRS_TO_ADD_PROPERTY)
.description("Controls/limits the number of FlowFile attributes that are added when "
+ "multiple thresholds are exceeded. Recall that when a threshold is exceeded, "
+ "a pair of two attributes are added to the FlowFile, one is the current count and "
+ "the other is the threshold. If 100 thresholds are exceeded, then 200 "
+ "attributes will be added to the FlowFile. Setting this property to zero means add "
+ "all count/threshold pairs that were exceeded. Any setting greater than zero indicates "
+ "how many count/threshold pairs to add when multiple thresholds are exceeded. "
+ "Only non-negative settings are supported. The default is 0. "
+ "In effect only when '" + ADD_ATTRIBUTES_PROPERTY + "' is '" + ONLY_WHEN_THRESHOLD_EXCEEDED + "'."
+ "Setting " + ADD_ATTRIBUTES_PROPERTY + " to" + " '" + ALWAYS + "' or '" + NEVER
+ "' causes '" + MAX_ATTRIBUTE_PAIRS_TO_ADD_PROPERTY + "' to be ignored.")
.required(false)
.addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
.defaultValue("0")
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES = new PropertyDescriptor.Builder()
.name(DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES_PROPERTY)
.description("The delimiter to use for naming threshold exceeded attributes,"
+ "e.g., fileCount.attributeName.value. Defaults to \'.\'")
.required(false)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.defaultValue(DEFAULT_DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES)
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES = new PropertyDescriptor.Builder()
.name(ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES_PROPERTY)
.description("Setting this property allows a FlowFile attribute to be used for counting bytes, "
+ "in place of the actual fileSize. Note that the attribute named by this property "
+ "must contain only numeric, non-negative integer values for each FlowFile. "
+ "Non-numeric or negative values that are encountered will be ignored, causing the actual fileSize "
+ "to be used instead.")
.required(false)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.defaultValue(STANDARD_FLOWFILE_FILESIZE_PROPERTY)
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_FREQUENCY_TO_SAVE_COUNTS_SECS = new PropertyDescriptor.Builder()
.name(FREQUENCY_TO_SAVE_COUNTS_PROPERTY)
.description("How often the counts should be written to disk. The default value is 30 seconds.")
.required(false)
.addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
.defaultValue("30")
.expressionLanguageSupported(false)
.build();
public static final PropertyDescriptor OPT_COUNTS_PERSISTENCE_FILE_PREFIX = new PropertyDescriptor.Builder()
.name(PREFIX_FOR_COUNTS_PERSISTENCE_FILE_PROPERTY)
.description("The prefix for the file that is saved (to disk) to maintain counts across NIFI restarts. "
+ "By default, the value is " + DEFAULT_COUNTS_PERSISTENCE_FILE_PREFIX + ". The actual name of the counts file will "
+ "be this value plus \"-XXXX.counts\" where XXXX is the processor ID.")
.required(false)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.defaultValue(DEFAULT_COUNTS_PERSISTENCE_FILE_PREFIX)
.expressionLanguageSupported(false)
.build();
// relationships
private final AtomicReference<Set<Relationship>> relationships = new AtomicReference<>();
public static final String SUCCESS = "success";
public static final Relationship RELATIONSHIP_SUCCESS = new Relationship.Builder()
.name(SUCCESS)
.description("All FlowFiles follow this relationship, unless there is a problem with the FlowFile.")
.build();
public static final String FAILURE = "failure";
public static final Relationship RELATIONSHIP_FAILURE = new Relationship.Builder()
.name(FAILURE)
.description("FlowFiles follow this relationship path if there is a problem with the FlowFile, or if there is a unrecoverable configuration error.")
.build();
@Override
public Collection<ValidationResult> customValidate(ValidationContext validationContext) {
final Collection<ValidationResult> problems = new ArrayList<>();
String countResetTime = validationContext.getProperty(OPT_COUNT_RESET_TIME).getValue();
String minutesToWaitBeforeResettingCounts = validationContext.getProperty(OPT_COUNT_RESET_MINUTES).getValue();
if (countResetTime == null && minutesToWaitBeforeResettingCounts == null) {
ValidationResult rolloverTimeOrMinutesRequired = new ValidationResult.Builder()
.subject("Invalid processor configuration.")
.explanation("To prevent counts from increasing forever, you must specify either a '" + COUNT_RESET_TIME_PROPERTY + "' or '" + MINUTES_TO_WAIT_BEFORE_RESETTING_COUNTS_PROPERTY + "'.")
.input(COUNT_RESET_TIME_PROPERTY + ": " + countResetTime + " "
+ MINUTES_TO_WAIT_BEFORE_RESETTING_COUNTS_PROPERTY + ": " + minutesToWaitBeforeResettingCounts)
.valid(false)
.build();
problems.add(rolloverTimeOrMinutesRequired);
}
int maxPairsToAdd = Integer.parseInt(validationContext.getProperty(OPT_MAX_ATTRIBUTE_PAIRS_TO_ADD_WHEN_MULTIPLE_THRESHOLD_EXCEEDED).getValue());
if (maxPairsToAdd < 0) {
ValidationResult maxPairsToAddCannotBeNegative = new ValidationResult.Builder()
.subject("Invalid processor configuration.")
.explanation(MAX_ATTRIBUTE_PAIRS_TO_ADD_PROPERTY + " cannot be negative.")
.input(MAX_ATTRIBUTE_PAIRS_TO_ADD_PROPERTY + ": " + maxPairsToAdd)
.valid(false)
.build();
problems.add(maxPairsToAddCannotBeNegative);
}
return problems;
}
@Override
protected void init(final ProcessorInitializationContext context) {
final List<PropertyDescriptor> properties = new ArrayList<>();
properties.add(OPT_AGGREGATE_COUNTS_WHEN_NO_THRESHOLD_PROVIDED);
properties.add(OPT_COUNT_RESET_TIME);
properties.add(OPT_COUNT_RESET_MINUTES);
properties.add(OPT_ADD_ATTRIBUTES);
properties.add(OPT_MAX_ATTRIBUTE_PAIRS_TO_ADD_WHEN_MULTIPLE_THRESHOLD_EXCEEDED);
properties.add(OPT_DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES);
properties.add(OPT_ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES);
properties.add(OPT_FREQUENCY_TO_SAVE_COUNTS_SECS);
properties.add(OPT_COUNTS_PERSISTENCE_FILE_PREFIX);
this.properties = Collections.unmodifiableList(properties);
final Set<Relationship> relationships = new HashSet<>();
relationships.add(RELATIONSHIP_SUCCESS);
relationships.add(RELATIONSHIP_FAILURE);
this.relationships.set(Collections.unmodifiableSet(relationships));
}
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return properties;
}
@Override
public Set<Relationship> getRelationships() {
return relationships.get();
}
@OnStopped
public void processorTasksStopped() {
writeLock.lock();
try {
// Since the user can change thresholds when the processor is stopped,
// force the user thresholds to be reloaded on the next call to onTrigger()
this.thresholds = null;
} finally {
writeLock.unlock();
}
}
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
final ProcessorLog logger = getLogger();
//ProvenanceReporter provenanceReporter = session.getProvenanceReporter();
getCounts(context, session, logger);
if (shouldClearCounts(context)) {
clearCounts(context, session, logger);
}
if (shouldSaveCounts(context)) {
saveCounts(getCountsFile(context), logger);
}
FlowFile flowFile = null;
try {
final Map<String, Map<String, CompareRecord>> thresholds = getThresholds(context, logger);
if (thresholds == null) {
logger.error(this + " unable to read thresholds.");
return;
}
flowFile = session.get();
if (flowFile == null) {
return;
}
// prep to loop through all thresholds for this FlowFile
final long fileSize = getFileSize(context, logger, flowFile);
final String delimiter = context.getProperty(OPT_DELIMITER_TO_USE_FOR_COUNTS_ATTRIBUTES).getValue();
boolean aggregateCountsWhenNoThresholdProvided = Boolean.parseBoolean(context.getProperty(OPT_AGGREGATE_COUNTS_WHEN_NO_THRESHOLD_PROVIDED).getValue());
String addAttributes = context.getProperty(OPT_ADD_ATTRIBUTES).getValue();
int maxPairsToAdd = Integer.parseInt(context.getProperty(OPT_MAX_ATTRIBUTE_PAIRS_TO_ADD_WHEN_MULTIPLE_THRESHOLD_EXCEEDED).getValue());
int countOfPairsAdded = 0;
final List<String> reasonsThresholdsExceeded = new ArrayList<>();
long numApplicableThresholds = 0, numThresholdsExceeded = 0;
// loop through all thresholds for this FlowFile
for (final String attributeName : thresholds.keySet()) { //user configured thresholds
String attributeValue = flowFile.getAttribute(attributeName);
if (attributeValue == null) { // attribute does not exist on this FlowFile
continue; // check for any remaining thresholds that might apply to attributes on this FlowFile
}
CompareRecord threshold = thresholds.get(attributeName).get(attributeValue); //has (user configured) thresholds only, not actual counts
CompareRecord countsForAttrValue = null;
if (threshold == null) { //user did not specify a threshold for the attributeValue found in the FlowFile
threshold = thresholds.get(attributeName).get(DEFAULT_THRESHOLDS_KEY); //use default thresholds
if (aggregateCountsWhenNoThresholdProvided) {
// ensure there is a default count record
countsForAttrValue = getCountsForAttributeValue(getCountsForAttribute(attributeName), DEFAULT_THRESHOLDS_KEY, attributeName, threshold); // if no default count record available, one will be created
} else {
countsForAttrValue = getCountsForAttributeValue(getCountsForAttribute(attributeName), attributeValue, attributeName, threshold); // if no count record available, one will be created
}
} else { // there is a user specified threshold
countsForAttrValue = getCountsForAttributeValue(getCountsForAttribute(attributeName), attributeValue, attributeName, threshold); // if no count record available, one will be created
}
// update internal (processor) counts
countsForAttrValue.addToFlowFileCounts(fileSize);
String attributeNameAndValue = delimiter + countsForAttrValue.getAttributeName() + delimiter + attributeValue;
if (addAttributes.equals(ALWAYS)) {
flowFile = session.putAttribute(flowFile, FILE_COUNT_PREFIX + attributeNameAndValue, Integer.toString(countsForAttrValue.getFileCount()));
flowFile = session.putAttribute(flowFile, FILE_THRESHOLD_PREFIX + attributeNameAndValue, Integer.toString(countsForAttrValue.getFileThreshold()));
countOfPairsAdded++;
flowFile = session.putAttribute(flowFile, BYTE_COUNT_PREFIX + attributeNameAndValue, Long.toString(countsForAttrValue.getByteCount()));
flowFile = session.putAttribute(flowFile, BYTE_THRESHOLD_PREFIX + attributeNameAndValue, Long.toString(countsForAttrValue.getByteThreshold()));
countOfPairsAdded++;
}
// check thresholds
if (countsForAttrValue.fileThresholdExceeded()) {
numThresholdsExceeded++;
reasonsThresholdsExceeded.add(countsForAttrValue.getReasonFileLimitExceeded());
if (addAttributes.equals(ONLY_WHEN_THRESHOLD_EXCEEDED) && (maxPairsToAdd >= 0) && ((maxPairsToAdd == 0) || (countOfPairsAdded < maxPairsToAdd))) {
//String attributeNameAndValue = delimiter + countsForAttrValue.getAttributeName() + delimiter + attributeValue;
flowFile = session.putAttribute(flowFile, FILE_COUNT_PREFIX + attributeNameAndValue, Integer.toString(countsForAttrValue.getFileCount()));
flowFile = session.putAttribute(flowFile, FILE_THRESHOLD_PREFIX + attributeNameAndValue, Integer.toString(countsForAttrValue.getFileThreshold()));
countOfPairsAdded++;
}
}
if (countsForAttrValue.byteThresholdExceeded()) {
numThresholdsExceeded++;
reasonsThresholdsExceeded.add(countsForAttrValue.getReasonByteLimitExceeded());
if (addAttributes.equals(ONLY_WHEN_THRESHOLD_EXCEEDED) && (maxPairsToAdd >= 0) && ((maxPairsToAdd == 0) || (countOfPairsAdded < maxPairsToAdd))) {
//String attributeNameAndValue = delimiter + countsForAttrValue.getAttributeName() + delimiter + attributeValue;
flowFile = session.putAttribute(flowFile, BYTE_COUNT_PREFIX + attributeNameAndValue, Long.toString(countsForAttrValue.getByteCount()));
flowFile = session.putAttribute(flowFile, BYTE_THRESHOLD_PREFIX + attributeNameAndValue, Long.toString(countsForAttrValue.getByteThreshold()));
countOfPairsAdded++;
}
}
numApplicableThresholds = numApplicableThresholds + 2; // the UI requires that both a file and byte threshold be supplied
} // end loop through all attributes with thresholds
if (numThresholdsExceeded > 0) {
logLimitsWereExceeded(logger, flowFile, reasonsThresholdsExceeded);
}
flowFile = session.putAttribute(flowFile, NUM_APPLICABLE_THRESHOLDS_KEY, Long.toString(numApplicableThresholds));
flowFile = session.putAttribute(flowFile, NUM_THRESHOLDS_EXCEEDED_KEY, Long.toString(numThresholdsExceeded));
session.transfer(flowFile, RELATIONSHIP_SUCCESS);
} catch (final Throwable t) {
if (flowFile != null) {
logger.warn("Failed to process " + flowFile + ". Sending to failure relationship.", t);
session.transfer(flowFile, RELATIONSHIP_FAILURE);
}
}
} //end onTrigger(...)
private void logLimitsWereExceeded(ProcessorLog logger, final FlowFile flowFile, final List<String> reasonsThresholdExceeded) {
final StringBuilder sb = new StringBuilder();
sb.append("Threshold(s) Exceeded for: ").append(flowFile);
int numberOfReasons = reasonsThresholdExceeded.size();
for (int i = 0; i < numberOfReasons; i++) {
sb.append(reasonsThresholdExceeded.get(i));
}
logger.info("\n" + sb.toString());
}
private long getFileSize(final ProcessContext context, final ProcessorLog logger, FlowFile flowFile) {
long fileSize = flowFile.getSize();
String attributeToUseForCountingBytes = context.getProperty(OPT_ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES).getValue();
if (attributeToUseForCountingBytes != null) {
if (attributeToUseForCountingBytes.equals(STANDARD_FLOWFILE_FILESIZE_PROPERTY)) {
return fileSize;
}
final String valueToUseForCountingBytes = flowFile.getAttribute(attributeToUseForCountingBytes);
if (valueToUseForCountingBytes != null) {
try {
final int intVal = Integer.parseInt(valueToUseForCountingBytes);
if (intVal >= 0) {
fileSize = intVal;
} else {
logger.warn(this + " Value to use for counting bytes must be numeric and non-negative. Found: " + valueToUseForCountingBytes);
}
} catch (final NumberFormatException e) {
logger.warn(this + " Value to use for counting bytes must be numeric and non-negative. Found: " + valueToUseForCountingBytes);
}
} else {
logger.warn(this + " Value to use for counting bytes cannot be null. It must be numeric and non-negative. Found: " + valueToUseForCountingBytes);
}
} else {
logger.warn(this + ATTRIBUTE_TO_USE_FOR_COUNTING_BYTES_PROPERTY + " property cannot be empty.");
}
return fileSize;
}
private ConcurrentHashMap<String, CompareRecord> getCountsForAttribute(final String attributeName) {
ConcurrentHashMap<String, CompareRecord> countsForThisAttribute = null;
readLock.lock();
try {
countsForThisAttribute = this.counts.get(attributeName);
} finally {
readLock.unlock();
}
if (countsForThisAttribute == null) { // if no count record is available, create one
countsForThisAttribute = new ConcurrentHashMap<>();
ConcurrentHashMap<String, CompareRecord> putResult = null;
writeLock.lock();
try {
putResult = this.counts.putIfAbsent(attributeName, countsForThisAttribute);
} finally {
writeLock.unlock();
}
if (putResult != null) {
countsForThisAttribute = putResult;
}
}
return countsForThisAttribute;
}
private CompareRecord getCountsForAttributeValue(ConcurrentHashMap<String, CompareRecord> countsForThisAttribute,
final String attributeValue, final String attributeName, final CompareRecord threshold) {
CompareRecord countsForThisAttributeValue = null;
readLock.lock();
try {
countsForThisAttributeValue = countsForThisAttribute.get(attributeValue);
} finally {
readLock.unlock();
}
if (countsForThisAttributeValue == null) { // if no count record is available, create one
countsForThisAttributeValue = new CompareRecord(threshold, attributeName, attributeValue);
CompareRecord returnedState = null;
writeLock.lock();
try {
returnedState = countsForThisAttribute.putIfAbsent(attributeValue, countsForThisAttributeValue);
} finally {
writeLock.unlock();
}
if (returnedState != null) {
countsForThisAttributeValue = returnedState;
}
}
return countsForThisAttributeValue;
}
protected boolean shouldSaveCounts(ProcessContext context) {
writeLock.lock();
try {
if (this.timeCountsLastSaved == null) {
this.timeCountsLastSaved = System.currentTimeMillis();
}
final String saveSeconds = context.getProperty(OPT_FREQUENCY_TO_SAVE_COUNTS_SECS).getValue();
final int seconds = saveSeconds == null ? DEFAULT_SAVE_COUNTS_FREQ_SECS : Integer.parseInt(saveSeconds);
final long timeToSave = seconds * 1000 + this.timeCountsLastSaved;
return timeToSave <= System.currentTimeMillis();
} finally {
writeLock.unlock();
}
}
protected void saveCounts(final File file, ProcessorLog logger) {
readLock.lock();
try {
if (this.counts == null) {
return;
}
try {
final FileOutputStream fos = new FileOutputStream(file);
final ObjectOutputStream oos = new ObjectOutputStream(fos);
try {
oos.writeObject(this.counts);
Long time = this.timeCountsLastCleared;
if (time == null) {
time = 0L;
}
oos.writeObject(time);
} finally {
FileUtils.closeQuietly(oos);
}
logger.debug(this + " saved current counts to file " + file.getAbsolutePath());
} catch (final IOException e) {
logger.error("Unable to save counts to file " + file.getAbsolutePath() + " due to " + e);
}
} finally {
readLock.unlock();
}
writeLock.lock();
try {
this.timeCountsLastSaved = System.currentTimeMillis();
} finally {
writeLock.unlock();
}
}
protected synchronized ConcurrentHashMap<String, ConcurrentHashMap<String, CompareRecord>> getCounts(final ProcessContext context, final ProcessSession session, final ProcessorLog logger) {
if (this.counts != null) {
return this.counts;
}
final File file = getCountsFile(context);
if (file.exists()) {
try {
final Tuple<Long, ConcurrentHashMap<String, ConcurrentHashMap<String, CompareRecord>>> tuple = readCountsFile(file);
writeLock.lock();
try {
timeCountsLastCleared = tuple.getKey();
this.counts = tuple.getValue();
} finally {
writeLock.unlock();
}
if (shouldClearCounts(context)) {
logger.warn(this + " restored saved counts but the counts have already expired. Clearing counts now.");
clearCounts(context, session, logger);
} else {
logger.info(this + " restored saved counts");
}
return this.counts;
} catch (final IOException e) {
logger.error(this + " unable to read counts from file " + file.getAbsolutePath() + " due to " + e);
this.counts = new ConcurrentHashMap<>();
}
} else {
this.counts = new ConcurrentHashMap<>();
}
return this.counts;
}
private File getCountsFile(ProcessContext context) {
final String countsFilePrefixVal = context.getProperty(OPT_COUNTS_PERSISTENCE_FILE_PREFIX).getValue();
final String countsFilePrefix = countsFilePrefixVal == null ? DEFAULT_COUNTS_PERSISTENCE_FILE_PREFIX : countsFilePrefixVal;
final String countsFilename = countsFilePrefix + "-" + this.getIdentifier() + ".counts";
final File file = new File(countsFilename);
return file;
}
@SuppressWarnings("unchecked")
protected Tuple<Long, ConcurrentHashMap<String, ConcurrentHashMap<String, CompareRecord>>> readCountsFile(final File file)
throws IOException {
final FileInputStream fis = new FileInputStream(file);
final ObjectInputStream ois = new ObjectInputStream(fis);
try {
final Object mapObject = ois.readObject();
final Long rolloverTime = (Long) ois.readObject();
return new Tuple<>(rolloverTime, (ConcurrentHashMap<String, ConcurrentHashMap<String, CompareRecord>>) mapObject);
} catch (final ClassNotFoundException e) {
throw new IOException(e);
} finally {
FileUtils.closeQuietly(ois);
}
}
private Map<String, Map<String, CompareRecord>> getThresholds(ProcessContext context, ProcessorLog logger) {
Map<String, Map<String, CompareRecord>> thresholds;
readLock.lock();
try {
if (this.thresholds == null || this.thresholds.isEmpty()) {
thresholds = null;
} else {
thresholds = new HashMap<>(this.thresholds);
}
} finally {
readLock.unlock();
}
if (thresholds == null) {
writeLock.lock();
try {
if (this.thresholds == null) {
final ThresholdsParser parser = new ThresholdsParser(logger);
this.thresholds = parser.readThresholds(context.getAnnotationData()); //read thresholds from flow.xml file
}
thresholds = Collections.unmodifiableMap(this.thresholds);
} catch (final Exception e) {
logger.error("Unable to read Thresholds.", e);
return null;
} finally {
writeLock.unlock();
}
}
return thresholds;
}
protected boolean shouldClearCounts(ProcessContext context) {
readLock.lock();
try {
// If we are rolling over based on time, rather than a certain number of minutes,
// then we don't want to allow the millisToClear to be greater than 24 hours.
final boolean usingTime = (context.getProperty(OPT_COUNT_RESET_TIME).getValue() != null);
long millisToClear = calculateCountResetMillis(context);
if (usingTime) {
millisToClear = Math.min(MILLIS_IN_HOUR * 24, millisToClear);
}
long timeToClear = this.timeCountsLastCleared + millisToClear;
return timeToClear < System.currentTimeMillis();
} finally {
readLock.unlock();
}
}
protected long calculateCountResetMillis(ProcessContext context) {
final String rolloverTimeVal = context.getProperty(OPT_COUNT_RESET_TIME).getValue();
final String rolloverMinVal = context.getProperty(OPT_COUNT_RESET_MINUTES).getValue();
if (rolloverTimeVal == null) {
final int minutes = Integer.parseInt(rolloverMinVal);
final long millis = minutes * MILLIS_IN_MINUTE;
return millis;
} else {
final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
final String[] rolloverSplit = rolloverTimeVal.split(":");
final int rolloverHour = Integer.parseInt(rolloverSplit[0]);
final int rolloverMinutes = Integer.parseInt(rolloverSplit[1]);
final int rolloverSeconds = Integer.parseInt(rolloverSplit[2]);
calendar.set(Calendar.HOUR_OF_DAY, rolloverHour);
calendar.set(Calendar.MINUTE, rolloverMinutes);
calendar.set(Calendar.SECOND, rolloverSeconds);
calendar.set(Calendar.MILLISECOND, 0);
// Make sure we have the right day.
final Calendar lastRolloverCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
readLock.lock();
try {
lastRolloverCal.setTimeInMillis(this.timeCountsLastCleared);
} finally {
readLock.unlock();
}
while (calendar.before(lastRolloverCal)) {
calendar.add(Calendar.DAY_OF_YEAR, 1);
}
return calendar.getTimeInMillis() - lastRolloverCal.getTimeInMillis();
}
}
protected void clearCounts(final ProcessContext context, final ProcessSession session, final ProcessorLog logger) {
writeLock.lock();
try {
if (shouldClearCounts(context)) {
logger.info("\n" + this + " throttling period has elapsed; resetting counts.");
logCounts(context, logger);
counts.clear();
this.timeCountsLastCleared = System.currentTimeMillis();
}
} finally {
writeLock.unlock();
}
}
protected synchronized void logCounts(ProcessContext context, ProcessorLog logger) {
final StringBuilder sb = new StringBuilder();
sb.append("\n>>>>>>>> MonitorThreshold Report: <<<<<<<<");
for (String count : this.counts.keySet()) {
sb.append("\nAttribute with Threshold: ").append(count);
ConcurrentHashMap<String, CompareRecord> countsForThisAttribute = getCountsForAttribute(count);
for (String value : countsForThisAttribute.keySet()) {
sb.append("\nAttribute: ").append(count).append("\tValue: '").append(value).append("'");
CompareRecord countsForThisAttributeValue = countsForThisAttribute.get(value);
sb.append("\n\tFiles Threshold: ").append(countsForThisAttributeValue.getFileThreshold())
.append("\tBytes Threshold: ").append(countsForThisAttributeValue.getByteThreshold())
.append("\n\tNum files seen: ").append(countsForThisAttributeValue.getFileCount())
.append("\tNum bytes seen: ").append(countsForThisAttributeValue.getByteCount());
}
}
sb.append("\n>>>>>> End MonitorThreshold Report. <<<<<<");
String logReport = sb.toString();
logger.info(logReport);
}
// CompareRecord does double duty.
// A single CompareRecord instance is used to contain thresholds (but kept in the count variables).
// Other CompareRecord instances contain actual counts, each with a reference back to their 'governing' threshold instance.
public static class CompareRecord implements Serializable {
private static final long serialVersionUID = -2759458735761055366L;
private int fileCount;
private long byteCount;
private CompareRecord threshold = null; // reference to the instance containing the thresholds
private final String attributeName;
private final String attributeValue;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock compareRecordReadLock = rwLock.readLock();
private final Lock compareRecordWriteLock = rwLock.writeLock();
// Used for creating a 'threshold' record.
public CompareRecord(final String attributeName, final String attributeValue, final int fileThreshold, final long byteThreshold) {
this.attributeName = attributeName;
this.attributeValue = attributeValue;
this.fileCount = fileThreshold; // for threshold records, save thresholds in count fields
this.byteCount = byteThreshold; // for threshold records, save thresholds in count fields
}
// Used for creating a 'count' record (that is compared against thresholds to determine whether or not a FlowFile exceeds a threshold).
public CompareRecord(final CompareRecord threshold, final String attributeName, final String attributeValue) {
this(attributeName, attributeValue, 0, 0); // begin with 0 for the counts
this.threshold = threshold; // "connect" this count record to its corresponding threshold record
}
public void addToFlowFileCounts(final long fileSize) {
if (threshold == null) {
throw new IllegalStateException("Attempted to add to counts when no threshold exists!");
}
compareRecordWriteLock.lock();
try {
byteCount += fileSize;
fileCount++;
} finally {
compareRecordWriteLock.unlock();
}
}
public String getReasonFileLimitExceeded() {
long fileCnt = getFileCount();
long fileThreshold = getFileThreshold();
if (getFileThreshold() < fileCnt) {
return "\n\tAttribute: '" + attributeName + "' Value '" + attributeValue + "'"
+ "\tFile threshold: " + fileThreshold
+ "\tExpected Count: " + fileCnt + " (counting this file)"
+ ((fileThreshold < fileCnt) ? "\t[threshold exceeded]" : "\t[below thresholds]");
}
return null;
}
public String getReasonByteLimitExceeded() {
long byteCnt = getByteCount();
long byteThreshold = getByteThreshold();
if (byteThreshold < byteCnt) {
return "\n\tAttribute: '" + attributeName + "' Value '" + attributeValue + "'"
+ "\tByte threshold: " + byteThreshold
+ "\tExpected Count: " + byteCnt + " (counting this file)"
+ ((byteThreshold < byteCnt) ? "\t[threshold exceeded]" : "\t[below thresholds]");
}
return null;
}
public boolean fileThresholdExceeded() {
return getFileThreshold() < getFileCount();
}
public boolean byteThresholdExceeded() {
return getByteThreshold() < getByteCount();
}
public long getByteCount() {
compareRecordReadLock.lock();
try {
return byteCount;
} finally {
compareRecordReadLock.unlock();
}
}
public int getFileCount() {
compareRecordReadLock.lock();
try {
return fileCount;
} finally {
compareRecordReadLock.unlock();
}
}
public long getByteThreshold() {
compareRecordReadLock.lock();
try {
return threshold.getByteCount();
} finally {
compareRecordReadLock.unlock();
}
}
public int getFileThreshold() {
compareRecordReadLock.lock();
try {
return threshold.getFileCount();
} finally {
compareRecordReadLock.unlock();
}
}
public String getAttributeName() {
return attributeName;
}
public String getAttributeValue() {
return this.attributeValue;
}
} //end CompareRecord inner class
} // end MonitorThreshold class

View File

@ -1,134 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.monitor;
/*
* NOTE: rule is synonymous with threshold
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.nifi.logging.ProcessorLog;
import org.apache.nifi.processors.monitor.MonitorThreshold.CompareRecord;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class ThresholdsParser {
private final ProcessorLog logger;
public ThresholdsParser(ProcessorLog logger) {
this.logger = logger;
}
private CompareRecord parseThresholds(final String attributeName, final Node valueNode) {
final NamedNodeMap attributes = valueNode.getAttributes();
final long byteLimit = Long.parseLong(attributes.getNamedItem("size").getTextContent());
final int filesLimit = Integer.parseInt(attributes.getNamedItem("count").getTextContent());
String value = null;
if (valueNode.getNodeName().equals("noMatchRule")) {
value = MonitorThreshold.DEFAULT_THRESHOLDS_KEY;
} else {
value = attributes.getNamedItem("id").getTextContent();
}
if (value == null) {
throw new IllegalArgumentException("Thresholds for " + attributeName + ", size=" + byteLimit + ", count=" + filesLimit + " has no ID");
}
final CompareRecord thresholdsState = new CompareRecord(attributeName, value, filesLimit, byteLimit);
return thresholdsState;
}
public Map<String, Map<String, CompareRecord>> readThresholds(String values)
throws ParserConfigurationException, SAXException, IOException {
final Map<String, Map<String, CompareRecord>> thresholdsMap = new HashMap<>();
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = factory.newDocumentBuilder();
final Document doc = db.parse(new InputSource(new ByteArrayInputStream(values.getBytes())));
final Element configurationElement = doc.getDocumentElement();
final NodeList secondLevelElementList = configurationElement.getChildNodes();
if (secondLevelElementList.getLength() == 0) {
logger.warn("Trying to read thresholds, but no thresholds were parsed.");
}
// Loop through the the list of FlowFile attributes (i.e., <flowFileAttribute> elements) and their list of values with limits
for (int i = 0; i < secondLevelElementList.getLength(); i++) {
final Node element = secondLevelElementList.item(i);
final String localName = element.getNodeName();
if (localName != null && localName.equalsIgnoreCase("flowFileAttribute")) {
if (element.getNodeType() != Node.ELEMENT_NODE) {
logger.debug("Encountered non-ELEMENT_NODE while parsing thresholds: " + element.getNodeName());
continue;
}
final String attributeName = element.getAttributes().getNamedItem("attributeName").getNodeValue();
final NodeList children = element.getChildNodes();
CompareRecord noMatchRule = null;
List<CompareRecord> thresholds = new ArrayList<>();
for (int j = 0; j < children.getLength(); j++) {
final Node valueNode = children.item(j);
if (valueNode == null || valueNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
final String nodeName = valueNode.getNodeName();
if (nodeName.equalsIgnoreCase("noMatchRule")) {
noMatchRule = parseThresholds(attributeName, valueNode);
} else if (nodeName.equalsIgnoreCase("rule")) {
thresholds.add(parseThresholds(attributeName, valueNode));
} else {
throw new SAXException("Invalid Threshold Configuration: child of 'attributeName' element was not 'noMatchRule', or 'threshold'");
}
}
if (noMatchRule == null) {
throw new SAXException("Invalid Threshold Configuration: no 'noMatchRule' element found for 'attributeName' element");
}
Map<String, CompareRecord> comparisonRecords = thresholdsMap.get(attributeName);
if (comparisonRecords == null) {
comparisonRecords = new HashMap<>();
thresholdsMap.put(attributeName, comparisonRecords);
}
comparisonRecords.put(noMatchRule.getAttributeValue(), noMatchRule);
for (final CompareRecord rs : thresholds) {
comparisonRecords.put(rs.getAttributeValue(), rs);
}
}
} // end loop through all listed attributes
return thresholdsMap;
}
}

View File

@ -1,15 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.apache.nifi.processors.monitor.MonitorThreshold

View File

@ -1,485 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<meta charset="utf-8" />
<title>MonitorThreshold</title>
<link rel="stylesheet" href="../css/component-usage.css" type="text/css" />
</head>
<body>
<h2>Description:</h2>
<p>This processor examines values found in selected FlowFile attributes and
maintains counts of the number of times the values have been encountered.
The counts are then used to check against user defined thresholds.
Both the counts and the thresholds can be exposed as FlowFile attributes, for
later examination and use by downstream processors.
</p>
<strong>Thresholds for Each Identified Attribute Value</strong>
<p>One or more FlowFile attributes can be identified for monitoring purposes, using
this processor’s advanced user interface (use the Advanced button when configuring the processor).
</p>
<p>In the following example, two FlowFile attributes have been identified: category and priority.
In other words, for each FlowFile that is presented to this processor both the category and priority
attributes (if they exist) will be examined to see if thresholds have been exceeded. For each attribute
that is identified, different thresholds (and counts) are used for every unique attribute value.
Finally, for each attribute value, two types of thresholds are supported; size (bytes) and number of files.
</p>
<p><strong>Example Thresholds:</strong></p>
<table border="1">
<tr><th>Attribute Names</th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th>File Threshold<th></tr>
<tr><td>category</td><td>Default</td><td>10000</td><td>100</td></tr>
<tr><td></td><td>red</td><td>1000</td><td>10</td></tr>
<tr><td></td><td>green</td><td>1000</td><td>5</td></tr>
<tr><th></th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th>File Threshold<th></tr>
<tr><td>priority</td><td>Default</td><td>100000</td><td>200</td></tr>
<tr><td></td><td>blue</td><td>1000</td><td>50</td></tr>
<tr><td></td><td>yellow</td><td>1000</td><td>100</td></tr>
</table>
<br><strong>When Thresholds are Exceeded</strong>
<p>
To allow downstream flows (processors) to react, this processor adds two new attributes
(<strong>numApplicableThresholds</strong> and <strong>numThresholdsExceeded</strong>) to
every FlowFile that passes through the processor, no matter whether a threshold is exceeded.
</p>
<p>
In addition, if a monitored attribute value is present on a FlowFile, and
<strong>'Add Attributes'</strong> is set to 'Only When Threshold Exceeded',
this processor will add an additional pair of attributes to each FlowFile that exceeds
the threshold; one attribute will contain the threshold and the other attribute will contain the count
(that exceeded the threshold), e.g.,
<ul>
<li>If a byte count threshold is exceeded, both
<ul>
<li><strong>byteCount<i>.attributeName.value</i></strong> and </li>
<li><strong>byteThreshold<i>.attributeName.value</i></strong> </li>
</ul>
attributes will be added to the FlowFile
</li>
<li>If a file count threshold is exceeded, both
<ul>
<li><strong>fileCount<i>.attributeName.value</i></strong> and </li>
<li><strong>fileThreshold<i>.attributeName.value</i></strong> </li>
</ul>
attributes will be added to the FlowFile,
where <strong><i>attributeName</i></strong> is replaced with the
actual attribute name and <strong><i>value</i></strong> is replaced with the actual attribute value.
</li>
</ul>
</p>
<p>For example, assuming the aforementioned thresholds (from the preceding table) are in effect, if
<ul>
<li>a FlowFile is encountered with a category value of red,</li>
<li>and it is the 11th such file encountered,</li>
</ul>
then the file count threshold (=10) would be exceeded.
This would cause the following new attributes to be added to the FlowFile:
<p>
<table border="1">
<tr><th>Attribute Name</th><th>Value</th></tr>
<tr><td><strong>fileCount.category.red</strong></td><td>11</td></tr>
<tr><td><strong>fileThreshold.category.red</strong></td><td>10</td></tr>
</table>
</p>
</p>
<p>
Note that when a threshold is exceeded, it is always noted in the
log, no matter the value of <strong>'Add Attributes'</strong>.
</p>
<p>
Finally, if a monitored attribute value is present on the FlowFile, and
<strong>'Add Attributes'</strong> is set to 'Always', <strong>four</strong> additional
attributes will be added to FlowFiles, <i>no matter whether a threshold has been exceeded</i>.
(This can be a convenient mechanism for counting values found in FlowFile attributes.)
<br>The four additional attributes are: <strong>fileCount<i>.attributeName.value</i></strong>,
a <strong>fileThreshold<i>.attributeName.value</i></strong>, a <strong>byteCount<i>.attributeName.value</i></strong>,
and a <strong>byteThreshold<i>.attributeName.value</i></strong> where <strong><i>attributeName</i></strong> is replaced with the
actual attribute name and <strong><i>value</i></strong> is replaced with the actual attribute value.
</p>
<p>
Note that a FlowFile must have a monitored attribute before corresponding count/threshold attributes will be added.
In other words, if a particular FlowFile does not have a monitored attribute, the associated
count/threshold attributes will not be added under any circumstances (no matter the value of 'Add Attributes').
</p>
<br><strong>When Multiple Thresholds are Exceeded</strong>
<p>
If more than one threshold is exceeded, a pair of attributes are added <i>for each</i> threshold
that is exceeded (subject to the values of the
<strong>'Add Attributes'</strong> and
<strong>'Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded' </strong>properties).
</p>
<br><strong>Default Thresholds Required</strong>
<p>
Since the range of possible values (for a particular FlowFile attribute) can be large/unknown
in advance, it is conceivable that thresholds may not be supplied for all possible values.
To account for this possibility, a <strong><i>default threshold must be specified for every attribute that is
monitored</i></strong>. Default thresholds are used when a "monitored" attribute has a value for
which no thresholds were supplied.
</p>
<p>
For example, in the following table, assume that thresholds have been specified for FlowFiles
with a category value of red or green, but no thresholds were specified for category values of cyan or magenta.
FlowFiles that are encountered with a category value of cyan or magenta will be checked using the default
thresholds.
</p>
<table border="1">
<tr><th>Attribute Names</th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th>File Threshold<th></tr>
<tr><td>category</td><td>Default</td><td>10000</td><td>100</td></tr>
<tr><td></td><td>red</td><td>1000</td><td>10</td></tr>
<tr><td></td><td>green</td><td>1000</td><td>5</td></tr>
<tr><td></td><td><strike>cyan value not supplied</strike></td><td>(use Default)</td><td>(use Default)</td></tr>
<tr><td></td><td><strike>magenta value not supplied</strike></td><td>(use Default)</td><td>(use Default)</td></tr>
<tr><th></th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th>File Threshold<th></tr>
<tr><td>priority</td><td>Default</td><td>100000</td><td>200</td></tr>
<tr><td></td><td>blue</td><td>1000</td><td>50</td></tr>
<tr><td></td><td>yellow</td><td>1000</td><td>100</td></tr>
</table>
<br><strong>Counting Without Thresholds</strong>
<p>
When a value (in a monitored attribute) is encountered, but no thresholds have been specified
for that value, the <strong>'Aggregate Counts When No Threshold Provided'</strong> determines how counts are maintained.
<ul>
<li>If false, then :
<ul>
<li>Separate, individual counts are maintained for each non-threshold, value that is encountered.
As shown in the following table, if a FlowFile has a category value of cyan, its counts would be added to the cyan counts.
If a FlowFile has a category value of magenta, its counts would be added to the magenta counts
- even though no thresholds were provided for category values cyan or magenta.
<table border="1">
<tr><th>Attribute Names</th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th><i>byte count</i></th><th>File Threshold</th><th><i>file count</i></th></tr>
<tr><td>category</td><td>Default</td><td>10000</td><td><strike>n/a</strike></td><td>100</td><td><strike>n/a</strike></td></tr>
<tr><td></td><td>red</td><td>1000</td><td></td><td>10</td><td></td></tr>
<tr><td></td><td>green</td><td>1000</td><td></td><td>5</td><td></td></tr>
<tr><td></td><td><strike>cyan value not supplied</strike></td><td>(use Default threshold)</td><td></td><td>(use Default threshold)</td><td></td></tr>
<tr><td></td><td><strike>magenta value not supplied</strike></td><td>(use Default threshold)</td><td></td><td>(use Default threshold)</td><td></td></tr>
<tr><th></th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th><i>byte count</i></th><th>File Threshold</th><th><i>file count</i></th></tr>
<tr><td>priority</td><td>Default</td><td>100000</td><td><strike>n/a</strike></td><td>200</td><td><strike>n/a</strike></td></tr>
<tr><td></td><td>blue</td><td>1000</td><td></td><td>50</td><td></td></tr>
<tr><td></td><td>yellow</td><td>1000</td><td></td><td>100</td><td></td></tr>
</table>
NOTE: Counts (shown in the above table) can <strong>not</strong> be viewed in the Advanced user interface. Only thresholds can be viewed in the Advanced user interface.
</li>
<li>When a non-threshold value is encountered, it's unique count will be compared with the default threshold.
So, if a FlowFile has a category value of cyan, the cyan count will be checked against the default thresholds (since no
thresholds were provided for a cyan category value). If a FlowFile has a category value of magenta, magenta
counts will be checked against the (same) default thresholds (since no thresholds were provided for a magenta category).
</li>
</ul>
</li>
<li>If true (default), then:
<ul>
<li>Counts for ALL non-threshold values are aggregated into a single default count. As shown in the following table,
all FlowFiles with a category value of cyan or magenta will have their counts added to a single, default count.</li>
<table border="1">
<tr><th>Attribute Names</th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th><i>byte count</i></th><th>File Threshold</th><th><i>file count</i></th></tr>
<tr><td>category</td><td>Default</td><td>10000</td><td></td><td>100</td><td></td></tr>
<tr><td></td><td>red</td><td>1000</td><td></td><td>10</td><td></td></tr>
<tr><td></td><td>green</td><td>1000</td><td></td><td>5</td><td></td></tr>
<tr><td></td><td><strike>cyan value not supplied</strike></td><td>(use Default threshold)</td><td>(use Default count)</td><td>(use Default threshold)</td><td>(use Default count)</td></tr>
<tr><td></td><td><strike>magenta value not supplied</strike></td><td>(use Default threshold)</td><td>(use Default count)</td><td>(use Default threshold)</td><td>(use Default count)</td></tr>
<tr><th></th><th>Attribute Values</th><th>Size Threshold (bytes)</th><th><i>byte count</i></th><th>File Threshold</th><th><i>file count</i></th></tr>
<tr><td>priority</td><td>Default</td><td>100000</td><td></td><td>200</td><td></td></tr>
<tr><td></td><td>blue</td><td>1000</td><td></td><td>50</td><td></td></tr>
<tr><td></td><td>yellow</td><td>1000</td><td></td><td>100</td><td></td></tr>
</table>
NOTE: Counts (shown in the above table) can <strong>not</strong> be viewed in the Advanced user interface. Only thresholds can be viewed in the Advanced user interface.
<li>When a non-threshold value is encountered, the default count (containing counts for both cyan and magenta
category values) will be compared to the default threshold.</li>
</ul>
</li>
</ul>
</p>
<p>NOTE 1: Counts are never maintained for attributes that have not been
identified for monitoring, i.e., do not have a default threshold. Once an attribute has
been identified for monitoring (has a default threshold), counts are always maintained
for thresholds that have been specified. The <strong>'Aggregate Counts When No Threshold Provided'</strong>
property only applies in situations where an attribute has been identified for monitoring, and a value is
encountered with no threshold.
</p>
<p>
NOTE 2: If separate counts are maintained for each unique value for each attribute with thresholds
(<strong>'Aggregate Counts When No Threshold Provided'</strong> is set to <strong>true</strong>),
consideration should be given to the choice of attributes to be monitored. An attribute that is
known to have a range of 1 million values, could (ultimately) result in this processor maintaining
2 million counts in memory.
If the range of values for an attribute are expected to be larger than available memory resources
(for maintaining counts), set the <strong>'Aggregate Counts When No Threshold Provided'</strong> property
to <strong>false</strong>. Adopting this approach will require that specific thresholds be provided for any
attribute values that should be "counted" separately.
</p>
<p><strong>Separate Instance Equals Separate Counts</strong></p>
<p>
Each MonitorThreshold instance maintains its own, separate counts. This is likely the expected
behavior when there are multiple MonitorThreshold instances on a flow. However, this has significant
implications when only a single MonitorThreshold instance appears on a flow, but the flow is running
on a NiFi <strong><i>cluster</i></strong>. In this case, each node (in the cluster) will have its
own instance of MonitorThreshold, each with its own,independent set of counts (unless MonitorThreshold is running isolated).
Assuming an even distribution of FlowFiles across all of the nodes in the
cluster will result in counts that increase slower, and therefore exceed
thresholds later, than might otherwise be expected. Consequently, thresholds may need to be set lower
in clustered environments, in order to achieve desired results.
</p>
<p><strong>Resetting Counts</strong></p>
<p>
To avoid counts that increase forever (that might eventually force all thresholds to be exceeded),
counts are reset on a periodic basis. Resets can be scheduled for a particular <strong>'Count Reset Time'</strong>
every day, or after a specific number of <strong>'Minutes to Wait Before Resetting Counts'</strong> have elapsed. At
least one value must be provided. If <strong>“Count Reset Timeâ€<C3A2></strong> is not provided, then the minutes to delay
are counted from the last reset.
</p>
<p>
When counts are reset, the current thresholds, total number of bytes and the number of files seen
for each Attribute and Value pair is printed in the logs, and the counts are then reset to zero.
</p>
<p><strong>Changing Thresholds</strong></p>
<p>
If changes are made to other parameters, such as the size or file thresholds,
after the processor has already been running, then the changes will not go into effect until the
Minutes to Wait… point has been reached. To circumvent this problem, consider temporarily changing the
<strong>'Count Reset Time'</strong> (and stop/restart the processor) to force the changes to be implemented sooner; then
revert back to the desired <strong>'Count Reset Time'</strong>.
</p>
<p>
<p><strong>Persisting Counts</strong></p>
<p>
To avoid unplanned count resets (due to system restarts, etc…), counts are persisted to disk on
a user defined periodic basis according to the <strong>'Frequency to Persist Counts (seconds)'</strong> property.
</p>
<p><strong>Modifies Attributes:</strong>
<table border="1">
<tr><th>Attribute Name</th><th>Description</th></tr>
<tr><td>numApplicableThresholds</td><td>The number of thresholds that were applicable for the FlowFile. Added to every FlowFile.</td></tr>
<tr><td>numThresholdsExceeded</td><td>The number of thresholds that the FlowFile exceeded. Added to every FlowFile.</td></tr>
<tr><td>fileThreshold<i>.attributeName.value</i></td><td>Contains the file threshold for an attribute name and value.
<br>A separate fileThreshold.attributeName.value attribute is added for each file threshold that is exceeded.
<br><i>attributeName</i> will be the name of the attribute who's file threshold was exceeded.
<br><i>value</i> will be the value of the attribute encountered on the FlowFile.
<br><br>Added <u>only</u> if <i>attributeName</i> is a monitored attribute <u>and</u> <i>attributeName</i>
is present on the FlowFile <u>and</u> one of the following is true:
<ul><li><strong>'Add Attributes'</strong> is set to 'Always' <u>or</u></li>
<li><strong>'Add Attributes'</strong> is set to 'Only When Threshold Exceeded' <u>and</u> a file threshold is exceeded.</li></ul>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(See also: <strong>'Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded'</strong>).
<br><br>Not added when <strong>'Add Attributes'</strong> is set to 'Never'
</td></tr>
<tr><td>fileCount<i>.attributeName.value</i></td><td>Contains the file count for an attribute name and value who's threshold was exceeded.
<br>A separate fileCount.attributeName.value attribute is added for each file threshold that is exceeded.
<br><i>attributeName</i> will be the name of the attribute who's file threshold was exceeded.
<br><i>value</i> will be the value of the attribute encountered on the FlowFile.
<br><br>Added <u>only</u> if <i>attributeName</i> is a monitored attribute <u>and</u> <i>attributeName</i>
is present on the FlowFile <u>and</u> one of the following is true:
<ul><li><strong>'Add Attributes'</strong> is set to 'Always' <u>or</u></li>
<li><strong>'Add Attributes'</strong> is set to 'Only When Threshold Exceeded' <u>and</u> a file threshold is exceeded.</li></ul>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(See also: <strong>'Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded'</strong>).
<br><br>Not added when <strong>'Add Attributes'</strong> is set to 'Never'
</td></tr>
<tr><td>byteThreshold<i>.attributeName.value</i></td><td>Contains the byte threshold for an attribute name and value.
<br>A separate byteThreshold.attributeName.value attribute is added for each byte threshold that is exceeded.
<br><i>attributeName</i> will be the name of the attribute who's byte threshold was exceeded.
<br><i>value</i> will be the value of the attribute encountered on the FlowFile.
<br><br>Added <u>only</u> if <i>attributeName</i> is a monitored attribute <u>and</u> <i>attributeName</i>
is present on the FlowFile <u>and</u> one of the following is true:
<ul><li><strong>'Add Attributes'</strong> is set to 'Always' <u>or</u></li>
<li><strong>'Add Attributes'</strong> is set to 'Only When Threshold Exceeded' <u>and</u> a file threshold is exceeded.</li></ul>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(See also: <strong>'Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded'</strong>).
<br><br>Not added when <strong>'Add Attributes'</strong> is set to 'Never'
</td></tr>
<tr><td>byteCount<i>.attributeName.value</i></td><td>Contains the byte count for an attribute name and value who's threshold was exceeded.
<br>A separate byteCount.attributeName.value attribute is added for each byte threshold is exceeded.
<br><i>attributeName</i> will be the name of the attribute who's byte threshold that was exceeded.
<br><i>value</i> will be the value of the attribute encountered on the FlowFile.
<br><br>Added <u>only</u> if <i>attributeName</i> is a monitored attribute <u>and</u> <i>attributeName</i>
is present on the FlowFile <u>and</u> one of the following is true:
<ul><li><strong>'Add Attributes'</strong> is set to 'Always' <u>or</u></li>
<li><strong>'Add Attributes'</strong> is set to 'Only When Threshold Exceeded' <u>and</u> a file threshold is exceeded.</li></ul>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(See also: <strong>'Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded'</strong>).
<br><br>Not added when <strong>'Add Attributes'</strong> is set to 'Never'
</td></tr>
</table>
<p><strong>Properties:</strong>
<br>Required properties appear in bold. Non-bold properties are optional.
If a property has a default, it is indicated.
If a property supports the NiFi Expression Language, it is indicated.
</p>
<p><ul>
<li>Aggregate Counts When No Threshold Provided</li>
<ul>
<li>When a value (in a monitored attribute) is encountered, but no thresholds have been specified
for that value, this property determines how counts are maintained.
<ul>
<li>If true (default):
<ul>
<li>Counts for ALL non-threshold values are aggregated into a single count.</li>
<li>When a non-threshold value is encountered, the aggregate count will be compared to the default threshold.</li>
</ul>
</li>
<li>If false:
<ul>
<li>Separate, individual counts are maintained for each non-threshold, value that is encountered.</li>
<li>When a non-threshold value is encountered, it's unique count will be compared with the default threshold.</li>
</ul>
</li>
</ul>
</p>
<p>NOTE 1: Counts are never maintained for attributes that have not been
identified for monitoring, i.e., do not have a default threshold. Once an attribute has
been identified for monitoring (has a default threshold), counts are always maintained
for thresholds that have been specified. This property only applies in situations
where an attribute has been identified for monitoring, and a value is encountered for which there is no user supplied threshold.
</p>
<p>NOTE 2: Changing this property from false to true will not remove unique, "non-threshold" counts
that are being maintained. It will simply suspend accumulating unique, "non-threshold" counts
and begin accumulating an aggregate/"default" count for all "non-threshold" values.
Setting the property back to false, will resume accumulating unique, "non-threshold" counts.
Since repeatedly changing this property, may give the mistaken appearance that counts are not
accurately maintained, it is advised that this property be set to one value and left unchanged
during a counting "period". See <strong>Count Reset Time</strong> and
<strong>Minutes to Wait Before Resetting Counts </strong> for information on starting a new counting period.
</p>
</li>
<li>Default: true</li>
<li>Supports expression language: false.</li>
</ul>
<li>Count Reset Time</li>
<ul>
<li>The time (24-hour clock, hh:mm:ss format, GMT Timezone) when the byte and file counts
will be reset to zero. All of hours, minutes, and seconds are required, i.e., hh:mm is insufficient.</li>
<li>Default: none – either <strong>Count Reset Time</strong> or <strong>Minutes to Wait Before Resetting Counts </strong>must be provided.</li>
<li>Supports expression language: false.</li>
</ul>
<li>Minutes to Wait Before Resetting Counts</li>
<ul>
<li>Minutes to delay count reset, beginning at Count Reset Time, if provided. If Count
Reset Time is not provided, then the minutes to delay are counted from the last reset.
(Last reset is initially set to the time when the processor is created (added to a flow).)
<br> NOTE: If changes are made to other
parameters, such as the size or file thresholds after the processor has already been running,
then the changes will not go into effect until this Minutes to Wait… point has been reached.
To circumvent this problem, consider temporarily changing the Count Reset Time (and
stop/restart the processor) to force the changes to be implemented sooner; then revert back
to the desired settings.
</li>
<li>Default: none – either <strong>Count Reset Time</strong> or <strong>Minutes to Wait
Before Resetting Counts </strong>must be provided.</li>
<li>Supports expression language: false.</li>
</ul>
<li>Add Attributes</li>
<ul>
<li>Setting this property to 'Only When Threshold Exceeded' (the default) will cause <strong>two</strong> additional attributes to be added to
FlowFiles for every threshold that is exceeded, <i>but only when the threshold is exceeded</i>.
<br>For example, if a file count threshold
is exceeded, a <strong>fileCount.attributeName.value</strong> and a <strong>fileThreshold.attributeName.value</strong>
will be added to the FlowFile.
<li>Setting this property to 'Always' will cause <strong>four</strong> additional attributes to be added to
FlowFiles <i>no matter whether the threshold has been exceeded</i>.
<br>The four additional attributes include a <strong>fileCount.attributeName.value</strong>,
a <strong>fileThreshold.attributeName.value</strong>, a <strong>byteCount.attributeName.value</strong>,
and a <strong>byteThreshold.attributeName.value</strong>.
<li>Setting this property to 'Never' means do not add additional attributes to FlowFiles.
<li>Note 1: Attributes are only added if a monitored attribute is present on the FlowFile, no matter
the value of this property.
<li>Note 2: This processor makes a log entry for every threshold
that is exceeded, no matter the value of this property.</li>
<li>Defaults to: 'Only When Threshold Exceeded'.</li>
<li>Supports expression language: false.</li>
</ul>
<li>Maximum Attribute Pairs to Add When Multiple Thresholds Exceeded</li>
<ul>
<li>Controls/limits the number of FlowFile attributes that are added when
multiple thresholds are exceeded.
<br>Recall that when a threshold is exceeded,
a pair of two attributes are added to the FlowFile, one is the current count and
the other is the threshold. If 100 thresholds are exceeded, then 200
attributes will be added to the FlowFile. Setting this property to zero means add
all count/threshold pairs that were exceeded. Any setting greater than zero indicates
how many count/threshold pairs to add when multiple thresholds are exceeded.
Only non-negative settings are supported. The default is 0.
<br>NOTE: This property is in effect only when <strong>'Add Attributes'</strong> is 'Only When Threshold Exceeded'.
In other words, setting <strong>'Add Attributes'</strong> to 'Always' or 'Never'
causes this property to be ignored.
</li>
<li>Defaults to: 0</li>
<li>Supports expression language: false.</li>
</ul>
<li>Delimiter to Use for Counts Attributes</li>
<ul>
</li><li>The delimiter to use when naming counts that are published as FlowFile
attributes, e.g., fileCount.attributeName.value.
</li><li>Defaults to: “.â€<C3A2>
</li><li>Supports expression language: false.
</ul>
<li>Attribute to use for Counting Bytes</li>
<ul>
<li>Setting this property allows a FlowFile attribute to be used for counting bytes,
in place of the actual fileSize. Note that the attribute named by this property must
contain only numeric, non-negative integer values for each FlowFile. Non-numeric or
negative values that are encountered will be ignored, causing the actual fileSize to
be used instead.</li>
<li>Defaults to: fileSize.</li>
<li>Supports expression language: false.</li>
</ul>
<li>Frequency to Save Counts (seconds)</li>
<ul>
<li>How often all of the counts should be written to disk.</li>
<li>Defaults to: 30 seconds.</li>
<li>Supports expression language: false.</li>
</ul>
<li>Prefix for Counts Persistence File </li>
<ul>
<li>The prefix for the file that is persisted to maintain counts across NIFI restarts.
The actual name of the state file will be this value plus "-XXXX.state" where XXXX is
the processor ID.</li>
<li>Defaults to: conf/MonitorThreshold</li>
<li>Supports expression language: false.</li>
</ul>
</ul></p>
<p>
<strong>Relationships:</strong>
</p>
<ul>
<li>success - all FlowFiles follow this relationship, unless there is a problem with the FlowFile.</li>
<li>failure - FlowFiles follow this relationship path if there is a problem processing the FlowFile, or if there is a unrecoverable configuration error.</li>
</ul>
</p>
</body>
</html>

View File

@ -1,23 +0,0 @@
This is the test file used in MonitorThreshold unit tests.
It's content is irrelevant (hence the gibberish below),
since the primary focus is on its attributes;
which are setup in the unit tests.
However, its size is relevant, as it affects the unit tests.
The size of this file should be 1,668 bytes.
There is nothing special about the number 1,668.
It just happened to be the size of the test file that was used for most of the unit tests for the precursor processor,
and it was easier to create a file containing 1,668 bytes than to modify all of the unit tests.
Besides, what difference does it make how big the test file is, as long as it is a known quantity.
1 2 3 4 5 6 7 8
012345678901234567890123456789012345678901234567890123456789012345678901234567890
dfadfsfgasdfgdgbhadg2346475789087908907u3aDFAdfu5675868978aswgt45yeryh56767768rt
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
DFSgfgfsd3578684686fghsd579057890fgadghadghsfyhjsffnsfgn65756757846956h6yh6h6sgh
DFSgfgfsd3578684686fghsd579057890fgadghadghsfyhjsffnsfgn65756757846956h6yh6h6sgh
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
dfsfg567878649468947957989078907890689068fjfhjshhjjtre57yjuyju469r4y5h65yuy65476
wx
01234567890123456789012345678901234567890123456789012345678901234567890123456789

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="32000" count="6">Default</noMatchRule>
<rule id="2345" size="1000000000" count="0"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="32000" count="6">Default</noMatchRule>
<rule id="2345" size="1000000000" count="1"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="32000" count="6">Default</noMatchRule>
<rule id="2345" size="10000" count="1000000000"/>
</flowFileAttribute>
</configuration>

View File

@ -1,26 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="32000" count="6">Default</noMatchRule>
<rule id="2345" size="10000" count="1000000000"/>
</flowFileAttribute>
<flowFileAttribute attributeName="priority" id="2">
<noMatchRule size="100000000" count="10000000">Default</noMatchRule>
<rule id="0" size="4000" count="1000000000"/>
<rule id="1" size="2000" count="1000000000"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="320000" count="6">Default</noMatchRule>
<rule id="2345" size="1669" count="2"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="320000" count="6">Default</noMatchRule>
<rule id="2345" size="1000000000" count="2"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="320000" count="2">Default</noMatchRule>
<rule id="1234" size="1000000000" count="9"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="3336" count="1000">Default</noMatchRule>
<rule id="1234" size="1000000000" count="9"/>
</flowFileAttribute>
</configuration>

View File

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="32000" count="6">Default</noMatchRule>
<rule id="" size="1668" count="1"/>
</flowFileAttribute>
</configuration>

View File

@ -1,22 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration>
<flowFileAttribute attributeName="category" id="1">
<noMatchRule size="1668" count="1">Default</noMatchRule>
<rule id=" " size="1668" count="1"/>
<rule id=" " size="1668" count="1"/>
</flowFileAttribute>
</configuration>

View File

@ -1,134 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>monitor-threshold-bundle</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>monitor-threshold-ui</artifactId>
<packaging>war</packaging>
<name>MonitorThreshold-UI</name>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/xsd</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<packageName>org.apache.nifi.settings.generated</packageName>
</configuration>
</execution>
</executions>
<configuration>
<generateDirectory>${project.build.directory}/generated-sources/jaxb</generateDirectory>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>javax.servlet.jsp.jstl-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>custom-ui-utilities</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,36 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
import java.util.Comparator;
import org.apache.nifi.settings.generated.Attribute;
public class AttributeComparator implements Comparator<Attribute> {
private final String column;
public AttributeComparator(String col) {
column = col;
}
@Override
public int compare(Attribute attr1, Attribute attr2) {
return attr1.getAttributeName().compareTo(attr2.getAttributeName());
}
}

View File

@ -1,242 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
/*
* NOTE: rule is synonymous with threshold
*/
import java.util.UUID;
import javax.net.ssl.SSLEngineResult.Status;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.settings.generated.ObjectFactory;
import org.apache.nifi.settings.generated.Rule;
import org.apache.nifi.settings.generated.Attribute;
/**
* MonitorThreshold REST Web Service (for handling changes to attributes made by
* the user in the advanced ui)
*
*/
@Path("/settings/processor/{procid}")
public class AttributeResource extends ThresholdSettingsResource {
@Context
private HttpServletRequest request;
@Context
private ServletContext servletContext;
public AttributeResource() {
super();
//logger.debug("Invoking AttributeResource.java AttributeResource() constructor.");
}
/**
* Returns a list of all attributes
*
* @param processorid
* @param sortcolumn
* @param sord
* @param attributefilter
* @param rowNum
* @param pageCommand
* @return
*/
@GET
@Path("/attributes")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
public Response getAllAttributes(@PathParam(PROCID) String processorid,
@QueryParam("sidx") @DefaultValue(ATTRIBUTENAME) String sortcolumn,
@QueryParam("sord") @DefaultValue("asc") String sord,
@QueryParam("attributefilter") @DefaultValue("") String attributefilter,
@QueryParam("rownum") @DefaultValue("-1") String rowNum,
@QueryParam("pagecom") @DefaultValue("") String pageCommand) {
//logger.debug("Invoking AttributeResource.java @GET @PATH(\"/attributes\"): getAllAttributes(...)");
Integer rownum = Integer.parseInt(rowNum);
return generateOkResponse(getConfigFile(request, servletContext).getConfigAttributes(sortcolumn, getIsAsc(sord), attributefilter, rownum, pageCommand)).build();
}
@GET
@Path("/attributeInfo")
@Produces(MediaType.WILDCARD)
public Response getAllAttributes(@PathParam(PROCID) String processorid) {
//logger.debug("Invoking AttributeResource.java @GET @PATH(\"/attributeInfo\"): getAllAttributes(...)");
return generateOkResponse(getConfigFile(request, servletContext).getListStats()).build();
}
/**
* Inserts/adds a new attribute.
*
* @param processorid
* @param attributeName
* @param size
* @param count
* @return
*/
@PUT
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/attribute")
public Response createAttribute(@PathParam(PROCID) String processorid,
@FormParam(ATTRIBUTENAME) @DefaultValue(StringUtils.EMPTY) String attributeName,
@FormParam(SIZE) @DefaultValue(StringUtils.EMPTY) String size,
@FormParam(COUNT) @DefaultValue(StringUtils.EMPTY) String count) {
//logger.debug("Invoking AttributeResource.java @PUT @PATH(\"/attribute\"): createAttribute(" + attributeName + ", " + size + ", " + count + ")");
try {
if (attributeName.isEmpty()) {
validation_error_list.add(INVALID_ATTRIBUTE_NAME);
}
if (!validateStringAsLong(size)) {
validation_error_list.add(INVALID_SIZE);
}
if (!validateStringAsInt(count)) {
validation_error_list.add(INVALID_COUNT);
}
ThresholdsConfigFile thresholds = getConfigFile(request, servletContext);
Attribute tg = thresholds.findAttributebyAttributeName(attributeName);
if (tg != null) {
validation_error_list.add(DUPLICATE_ATTRIBUTE_NAME + " " + attributeName);
}
if (!validation_error_list.isEmpty()) {
return Response.status(400).entity(setValidationErrorMessage(validation_error_list)).build();
}
ObjectFactory of = new ObjectFactory();
//create new attribute
Attribute attr = of.createAttribute();
attr.setAttributeName(attributeName);
attr.setId(UUID.randomUUID().toString());
//create default rule and add to attribute rules
Rule rule = of.createRule();
rule.setValue("Default");
rule.setCount(getBigIntValueOf(count));
rule.setSize(getBigIntValueOf(size));
attr.setNoMatchRule(rule);
thresholds.getFlowFileAttributes().add(attr);
//Save values to config file
thresholds.save();
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
/**
* Updates an attribute.
*
* @param processorid
* @param uuid
* @param attributeName
* @return
*/
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/attribute")
public Response updateAttribute(@PathParam(PROCID) String processorid,
@FormParam(ATTRIBUTE_UUID) String uuid,
@FormParam(ATTRIBUTENAME) @DefaultValue(StringUtils.EMPTY) String attributeName) {
//logger.debug("Invoking AttributeResource.java @POST @PATH(\"/attribute\"): updateAttribute(" + attributeName + ")");
try {
if (attributeName.isEmpty()) {
validation_error_list.add(INVALID_ATTRIBUTE_NAME);
}
ThresholdsConfigFile config = getConfigFile(request, servletContext);
Attribute tg = config.findAttributebyAttributeName(attributeName);
if (tg != null) {
if (tg.getId().compareTo(uuid) != 0) {
validation_error_list.add(DUPLICATE_ATTRIBUTE_NAME + " " + attributeName);
}
}
if (!validation_error_list.isEmpty()) {
return Response.status(400).entity(setValidationErrorMessage(validation_error_list)).build();
}
Attribute attr = config.findAttribute(uuid);
attr.setAttributeName(attributeName);
//Save values to config file
config.save();
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
/**
* Deletes an attribute.
*
* @param processorid
* @param uuid
* @return
*/
@DELETE
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/attribute")
public Response deleteAttribute(@PathParam(PROCID) String processorid,
@FormParam(ID) String uuid) {
//logger.debug("Invoking AttributeResource.java @DELETE @PATH(\"/attribute\"): deleteAttribute(...)");
try {
ThresholdsConfigFile config = getConfigFile(request, servletContext);
Attribute attr = config.findAttribute(uuid);
if (attr != null) {
config.getFlowFileAttributes().remove(attr);
//Save values to config file
config.save();
}
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
}

View File

@ -1,46 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
/*
* NOTE: rule is synonymous with threshold
*/
import java.util.Comparator;
import org.apache.nifi.settings.generated.Thresholds;
public class RuleComparator implements Comparator<Thresholds> {
private final String column;
public RuleComparator(String col) {
column = col;
}
@Override
public int compare(Thresholds rule1, Thresholds rule2) {
if (column.compareTo("attributevalue") == 0) {
return rule1.getId().compareTo(rule2.getId());
}
if (column.compareTo("size") == 0) {
return rule1.getSize().compareTo(rule2.getSize());
} else {
return rule1.getCount().compareTo(rule2.getCount());
}
}
}

View File

@ -1,227 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
/*
* NOTE: rule is synonymous with threshold
*/
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.settings.generated.Thresholds;
import org.apache.nifi.settings.generated.Attribute;
import org.apache.nifi.settings.generated.ObjectFactory;
import org.apache.nifi.settings.generated.Rule;
/**
* MonitorThreshold REST Web Service (for handling changes to thresholds made by
* the user in the Advanced UI)
*
*/
@Path("/settings/processor/{procid}/attribute/{attributeuuid}")
public class ThresholdResource extends ThresholdSettingsResource {
@Context
private HttpServletRequest request;
@Context
private ServletContext servletContext;
public ThresholdResource() {
super();
}
@GET
@Path("/rules")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
public Response getSubSettings(
@PathParam(PROCID) String processorid,
@PathParam(ATTRIBUTE_UUID) String attributeid,
@QueryParam("sidx") @DefaultValue(ID) String sortcolumn,
@QueryParam("sord") @DefaultValue("asc") String sord,
@QueryParam("attributevaluefilter") @DefaultValue("") String attributevaluefilter,
@QueryParam("sizefilter") @DefaultValue("") String sizefilter,
@QueryParam("filecountfilter") @DefaultValue("") String filecountfilter) {
//logger.debug("Invoking ThresholdResource.java @GET @PATH(\"/rules\"): getSubSettings(...)");
String result = getConfigFile(request, servletContext).getRules(attributeid, sortcolumn, getIsAsc(sord), attributevaluefilter, sizefilter, filecountfilter);
return generateOkResponse(result).build();
}
/**
* Inserts/adds a new threshold.
*
* @param processorid
* @param attributeid
* @param value
* @param size
* @param count
* @return
*/
@PUT
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/rule")
public Response ruleInsert(
@PathParam(PROCID) String processorid,
@PathParam(ATTRIBUTE_UUID) String attributeid,
@FormParam(RULEVALUE) @DefaultValue(StringUtils.EMPTY) String value,
@FormParam(SIZE) @DefaultValue(StringUtils.EMPTY) String size,
@FormParam(COUNT) @DefaultValue(StringUtils.EMPTY) String count) {
//logger.debug("Invoking ThresholdResource.java @PUT @PATH(\"/rule\"): ruleInsert(...). RULEVALUE is: " + value);
try {
ThresholdsConfigFile config = getConfigFile(request, servletContext);
if (value.isEmpty()) {
validation_error_list.add(INVALID_RULE_ID);
}
if (!validateStringAsLong(size)) {
validation_error_list.add(INVALID_SIZE);
}
if (!validateStringAsInt(count)) {
validation_error_list.add(INVALID_COUNT);
}
if (config.containsRule(attributeid, value)) {
validation_error_list.add(String.format(DUPLICATE_VALUE + "%s", value));
}
if (!validation_error_list.isEmpty()) {
return Response.status(400).entity(setValidationErrorMessage(validation_error_list)).build();
}
ObjectFactory of = new ObjectFactory();
//create new attribute
Thresholds rule = of.createThresholds();
rule.setId(value);
rule.setSize(getBigIntValueOf(size));
rule.setCount(getBigIntValueOf(count));
Attribute attr = config.findAttribute(attributeid);
attr.getRule().add(rule);
config.save();
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
/**
* Updates a threshold value, size or count.
*
* @param processorid
* @param uuid
* @param rulevalue
* @param size
* @param count
* @return
*/
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/rule/{" + RULEVALUE + "}")
public Response ruleUpdate(@PathParam(PROCID) String processorid,
@PathParam(ATTRIBUTE_UUID) String uuid,
// @PathParam(RULEUUID) String ruleuuid,
@PathParam(RULEVALUE) String rulevalue,
// @FormParam(RULEVALUE) @DefaultValue(StringUtils.EMPTY) String rulevalue,
@FormParam(SIZE) @DefaultValue(StringUtils.EMPTY) String size,
@FormParam(COUNT) @DefaultValue(StringUtils.EMPTY) String count) {
//logger.debug("Invoking ThresholdResource.java @POST @PATH(\"/rule/{\"+RULEVALUE+\"}\"): ruleUpdate(...). RULEVALUE is: " + rulevalue);
try {
// if(rulevalue.isEmpty()&& ruleuuid.compareTo("-1")!=0)
// validation_error_list.add(INVALIDRULEID);
if (!validateStringAsLong(size)) {
validation_error_list.add(INVALID_SIZE);
}
if (!validateStringAsInt(count)) {
validation_error_list.add(INVALID_COUNT);
}
if (!validation_error_list.isEmpty()) {
return Response.status(400).entity(setValidationErrorMessage(validation_error_list)).build();
}
ThresholdsConfigFile config = getConfigFile(request, servletContext);
Attribute attr = config.findAttribute(uuid);
// if(ruleuuid.compareTo("-1")==0){
if (rulevalue.compareToIgnoreCase("default") == 0) {
Rule rule = attr.getNoMatchRule();
rule.setCount(getBigIntValueOf(count));
rule.setSize(getBigIntValueOf(size));
} else {
Thresholds rule = config.findRule(attr, rulevalue);//ruleuuid);
// rule.setValue(rulevalue);
rule.setCount(getBigIntValueOf(count));
rule.setSize(getBigIntValueOf(size));
}
config.save();
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
/**
* Deletes a threshold.
*
* @param processorid
* @param uuid
* @param rulevalue
* @return
*/
@DELETE
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
@Path("/rule/{" + ID + "}")
public Response ruleDelete(@PathParam(PROCID) String processorid,
@PathParam(ATTRIBUTE_UUID) String uuid,
@PathParam(ID) String rulevalue) {
//logger.debug("Invoking ThresholdResource.java @DELETE @PATH(\"/rule/{\"+ID+\"}\"): ruleDelete(...). RULEVALUE is: " + rulevalue);
try {
ThresholdsConfigFile config = getConfigFile(request, servletContext);
Attribute attr = config.findAttribute(uuid);
Thresholds rule = config.findRule(attr, rulevalue);//ruleuuid);
attr.getRule().remove(rule);
config.save();
} catch (Exception ex) {
logger.error(ex.getMessage());
return Response.status(400).entity(GENERAL_ERROR + ex.getMessage()).build();
}
return generateOkResponse(Status.OK.toString()).build();
}
}

View File

@ -1,191 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
/*
* NOTE: rule is synonymous with threshold
*/
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.nifi.web.NiFiWebContext;
/**
* MonitorThreshold REST Web Service (superclass)
*/
public class ThresholdSettingsResource {
@Context
protected UriInfo context;
protected static final Logger logger = LoggerFactory.getLogger(ThresholdSettingsResource.class);
protected List<String> validation_error_list = new ArrayList<>();
protected static final String DUPLICATE_ATTRIBUTE_NAME = "Please enter a unique Attribute Name. An attribute with this name already exists: ";
protected static final String INVALID_ATTRIBUTE_NAME = "Attribute Name must contain a value.";
protected static final String INVALID_RULE_ID = "Please enter a value.";
protected static final String INVALID_SIZE = "Please enter an integer Size value in bytes. Punctuation (including decimals/commas) and other forms of text are not allowed. Max allowed size is 9223372036854775807.";
protected static final String INVALID_COUNT = "Please enter an integer File Count. Punctuation (including decimals/commas) and other forms of text are not allowed. Max allowed size is 2147483647.";
protected static final String GENERAL_ERROR = "A general error has occurred. Detailed Error Message: ";
protected static final String DUPLICATE_VALUE = "Please enter a unique value. A threshold with this value already exists: ";
protected static final String PROCID = "procid";
protected static final String ID = "id";
protected static final String ATTRIBUTE_UUID = "attributeuuid";//uuid of the attribute selected
protected static final String ATTRIBUTENAME = "attributename";//name of the attribute selected
protected static final String RULEUUID = "ruuid";
protected static final String RULEVALUE = "attributevalue";
protected static final String SIZE = "size";//rule/threshold size
protected static final String COUNT = "count";//rule/threshold count
/**
* Creates a new instance of ThresholdSettingsResource
*/
public ThresholdSettingsResource() {
}
/**
*
* @param response
* @return
*/
protected ResponseBuilder noCache(ResponseBuilder response) {
CacheControl cacheControl = new CacheControl();
cacheControl.setPrivate(true);
cacheControl.setNoCache(true);
cacheControl.setNoStore(true);
return response.cacheControl(cacheControl);
}
/**
* Returns a standard web response with a status 200.
*
* @param value
* @return
*/
protected ResponseBuilder generateOkResponse(String value) {
ResponseBuilder response = Response.ok(value);
return noCache(response);
}
/**
* Validates a number. If the string value cannot be parsed as a long then
* false is returned.
*
* @param value
* @return
*/
protected boolean validateStringAsLong(String value) {
try {
Long.parseLong(value);
} catch (NumberFormatException ex) {
logger.error(ex.getMessage());
return false;
}
return true;
}
/**
* Validates a number. If the string value cannot be parsed as an int then
* false is returned.
*
* @param value
* @return
*/
protected boolean validateStringAsInt(String value) {
try {
Integer.parseInt(value);
} catch (NumberFormatException ex) {
logger.error(ex.getMessage());
return false;
}
return true;
}
/**
* Validates a number. If the string value cannot be parsed as a float then
* false is returned.
*
* @param value
* @return
*/
protected boolean validateStringAsFloat(String value) {
try {
Float.parseFloat(value);
} catch (NumberFormatException ex) {
logger.error(ex.getMessage());
return false;
}
return true;
}
protected ThresholdsConfigFile getConfigFile(HttpServletRequest request, ServletContext servletContext) {
//logger.debug("Running ThresholdSettingsResource: getConfigFile(...).");
NiFiWebContext nifiContext = (NiFiWebContext) servletContext.getAttribute("nifi-web-context");
ThresholdsConfigFile file = new ThresholdsConfigFile(nifiContext, request);
return file;
}
protected Boolean getIsAsc(String sord) {
Boolean isAsc = true;
if (sord.compareTo("desc") == 0) {
isAsc = false;
}
return isAsc;
}
protected BigInteger getBigIntValueOf(String value) {
Long _value = Long.valueOf(value);
return BigInteger.valueOf(_value);
}
protected String setValidationErrorMessage(List<String> messages) {
String result = new String();
for (String message : messages) {
result += "<div>";
result += message;
result += "</div>";
}
return result;
}
@SuppressWarnings("unused")
private Map<String, String> getQueryStringValues(String querystring) {
Map<String, String> queryvalues = new HashMap<String, String>();
String[] splitvals = querystring.split("&");
queryvalues.put("processorid", splitvals[0]);
queryvalues.put(splitvals[1].split("=")[0], splitvals[1].split("=")[1]);
queryvalues.put(splitvals[2].split("=")[0], splitvals[2].split("=")[1]);
return queryvalues;
}
}

View File

@ -1,296 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.monitor.thresholds.ui;
/*
* NOTE: rule is synonymous with threshold
*/
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.nifi.settings.generated.Thresholds;
import org.apache.nifi.settings.generated.Attribute;
import org.apache.nifi.settings.generated.Configuration;
import org.apache.nifi.settings.generated.ObjectFactory;
import org.apache.nifi.web.HttpServletRequestContextConfig;
import org.apache.nifi.web.NiFiWebContext;
public class ThresholdsConfigFile {
protected final Logger logger = LoggerFactory.getLogger(ThresholdsConfigFile.class);
private final String THRESHOLD_SETTINGS_XSD = "/threshold_settings.xsd";
private final String JAXB_GENERATED_PATH = "org.apache.nifi.settings.generated";
private final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
private Configuration configuration;
private ObjectFactory objectFactory;
private final List<Attribute> filtered_attribute_list = new ArrayList<>();
private final NiFiWebContext nifiWebCtx;
private final HttpServletRequestContextConfig contextConfig;
private final HttpServletRequest request;
/**
* Load the JAXBContext.
*/
private JAXBContext initializeJaxbContext() {
try {
return JAXBContext.newInstance(JAXB_GENERATED_PATH);
} catch (JAXBException e) {
logger.error("Unable to create JAXBContext.");
throw new RuntimeException("Unable to create JAXBContext.");
}
}
public ThresholdsConfigFile(final NiFiWebContext ctx, final HttpServletRequest request) {
//logger.debug("Running ThresholdsConfigFile(...) constructor.");
nifiWebCtx = ctx;
this.request = request;
contextConfig = new HttpServletRequestContextConfig(request);
getState();
}
private void getState() {
objectFactory = new ObjectFactory();
String state = nifiWebCtx.getProcessor(contextConfig).getAnnotationData();
if (state != null && !state.isEmpty()) {
try {
//logger.debug("Running ThresholdsConfigFile.getState(). Getting (and unmarshalling) Annotation Data.");
// find the schema
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(ThresholdsConfigFile.class.getResource(THRESHOLD_SETTINGS_XSD));
// attempt to unmarshal
Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(schema);
JAXBElement<Configuration> element = unmarshaller.unmarshal(new StreamSource(new StringReader(state)), Configuration.class);
configuration = element.getValue();
} catch (Exception ex) {
logger.error(ex.getMessage());
}
} else {
try {
configuration = objectFactory.createConfiguration();
save();
} catch (Exception ex) {
logger.error(ex.getMessage());
}
}
}
public List<Attribute> getFlowFileAttributes() {
return configuration.getFlowFileAttribute();
}
/**
* Helper method to determine if an attribute is already in the attributes
* ArrayList.
*
* @param id
* @return
*/
public boolean containsAttribute(String id) {
boolean result = false;
for (Attribute attr : configuration.getFlowFileAttribute()) {
if (attr.getId().compareTo(id) == 0) {
result = true;
break;
}
}
return result;
}
public boolean containsRule(String id, String value) {
boolean result = false;
Attribute attr = findAttribute(id);
if (attr != null) {
Thresholds rule = findRule(attr, value);
if (rule != null) {
result = true;
}
}
return result;
}
/**
* Iterates through attributes list and returns the attribute that contains
* the id passed into the method. Returns null if attribute is not found in
* attribute list.
* @param id
* @return
*/
public Attribute findAttribute(String id) {
for (Attribute attr : configuration.getFlowFileAttribute()) {
if (attr.getId().compareTo(id) == 0) {
return attr;
}
}
return null;
}
public Attribute findAttributebyAttributeName(String attributeName) {
for (Attribute attr : configuration.getFlowFileAttribute()) {
if (attr.getAttributeName().compareTo(attributeName) == 0) {
return attr;
}
}
return null;
}
public Thresholds findRule(Attribute attr, String value) {
for (Thresholds rule : attr.getRule()) {
if (rule.getId().compareTo(value) == 0) {
return rule;
}
}
return null;
}
/**
* returns xml without attributes, used for presentation.
*
* @param col
* @param isAsc
* @param attributeFilter
* @param rowNum
* @param pageCom
* @return
*/
public String getConfigAttributes(String col, Boolean isAsc, String attributeFilter, Integer rowNum, String pageCom) {
return getFormattedAttributes(configuration.getFlowFileAttribute(), col, isAsc, attributeFilter, "", "", "");//,rowNum,pageCom);
}
/**
* UI Specific. Returns xml only related to the attributeid passed into the
* method.
* @param attrid
* @param col
* @param isAsc
* @param attributevalueFilter
* @param sizeFilter
* @param filecountfilter
* @return
*/
public String getRules(String attrid, String col, Boolean isAsc, String attributevalueFilter, String sizeFilter, String filecountfilter) {
List<Attribute> attributeList = new ArrayList<>();
Attribute attr = findAttribute(attrid);
if (attr != null) {
attributeList.add(attr);
}
Collections.sort(attributeList.get(0).getRule(), new RuleComparator(col));
if (!isAsc) {
Collections.reverse(attributeList.get(0).getRule());
}
return getFormattedAttributes(attributeList, col, isAsc, "", attributevalueFilter, sizeFilter, filecountfilter);//,-1,"");
}
public String getListStats()//Integer rowNum)
{
return String.format("Results %s", filtered_attribute_list.size());
}
private String getFormattedAttributes(List<Attribute> list, String col, Boolean isAsc, String attributeFilter, String attributevalueFilter,
String sizeFilter, String filecountfilter)//, Integer rowNum, String pageCom)
{
String result = "<?xml version=\"1.0\" ?><configuration><attributes>";
getFilteredList(list, attributeFilter);
for (Attribute attribute : getSortedPagedList(col, isAsc)) {
result += "<attribute>";
result += String.format("<uuid>%s</uuid>", attribute.getId());
result += String.format("<attributename>%s</attributename>", attribute.getAttributeName());
result += "<rules>";
result += "<rule>";
result += String.format("<ruuid>%s</ruuid>", UUID.randomUUID());
result += String.format("<attributevalue>Default</attributevalue>");
result += String.format("<size>%s</size>", attribute.getNoMatchRule().getSize());
result += String.format("<count>%s</count>", attribute.getNoMatchRule().getCount());
result += "</rule>";
for (Thresholds rule : attribute.getRule()) {
if (rule.getId().toLowerCase().contains(attributevalueFilter.toLowerCase())
&& rule.getSize().toString().contains(sizeFilter)
&& rule.getCount().toString().contains(filecountfilter)) {
result += "<rule>";
result += String.format("<ruuid>%s</ruuid>", UUID.randomUUID());
result += String.format("<attributevalue>%s</attributevalue>", rule.getId());
result += String.format("<size>%s</size>", rule.getSize());
result += String.format("<count>%s</count>", rule.getCount());
result += "</rule>";
}
}
result += "</rules></attribute>";
}
result += "</attributes></configuration>";
return result;
}
private void sortList(List<Attribute> list, String col, boolean isAsc) {
Collections.sort(list, new AttributeComparator(col));
if (!isAsc) {
Collections.reverse(list);
}
}
private void getFilteredList(List<Attribute> list, String attributeFilter) {
filtered_attribute_list.clear();
for (Attribute attribute : list) {
if (attribute.getAttributeName().toLowerCase().contains(attributeFilter.toLowerCase())) {
filtered_attribute_list.add(attribute);
}
}
}
private List<Attribute> getSortedPagedList(String col, boolean isAsc) {
sortList(filtered_attribute_list, col, isAsc);//sub, col, isAsc);
return filtered_attribute_list;// sub;
}
public void save() throws Exception {
final StringWriter strWriter = new StringWriter();
Marshaller marshaller = JAXB_CONTEXT.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.marshal(configuration, strWriter);
// save thresholds (in the framework/flow.xml) for MonitorThreshold processor to read and use.
nifiWebCtx.setProcessorAnnotationData(contextConfig, strWriter.toString());
}
}

View File

@ -1,15 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.apache.nifi.processors.monitor.MonitorThreshold

View File

@ -1,43 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="attribute-add-dialog">
<div class="dialog-content" >
<div id="attribute-error-message" class="error-message" ></div>
<div class="setting">
<div class="setting-name">Attribute Name</div>
<div class="setting-field">
<input id="new-attribute-name" name="new-property-name" style="width: 200px;" type="text"/>
</div>
</div>
<div id="setting-title">Enter default thresholds (required):</div>
<div class="setting">
<div class="setting-name">Default Size Threshold (bytes)</div>
<div class="setting-field">
<input id="size-value" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Default File Threshold</div>
<div class="setting-field">
<input id="file-count-value" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="attribute-confirm-dialog">
<div class="dialog-content">
<div id="attribute-dialog-content"></div>
</div>
</div>

View File

@ -1,27 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="attribute-edit-dialog">
<div class="dialog-content" >
<div id="attribute-error-message" class="error-message" ></div>
<div class="setting">
<div class="setting-name">Attribute Name</div>
<div class="setting-field">
<input id="edit-attribute-name" name="new-property-name" style="width: 200px;" type="text"/>
</div>
</div>
</div>
</div>

View File

@ -1,26 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="attribute-filter-dialog">
<div class="dialog-content" >
<div class="setting">
<div class="setting-name">Filter Attribute Name</div>
<div class="setting-field">
<input id="filter-attribute" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="error-dialog">
<div class="dialog-content">
<div id="error-dialog-content" ></div>
</div>
</div>

View File

@ -1,101 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Threshold Settings</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
<link rel="shortcut icon" href="images/nifi16.ico"/>
<link rel="stylesheet" href="../nifi/js/jquery/ui-smoothness/jquery-ui-1.10.4.min.css" type="text/css" />
<link rel="stylesheet" href="../nifi/js/jquery/modal/jquery.modal.css" type="text/css" />
<link rel="stylesheet" href="js/jquery/jqgrid/css/ui.jqgrid.css" type="text/css" />
<!-- Following is missing. Not sure where it might be. Will need to search further. -->
<link rel="stylesheet" href="../nifi/css/nf-canvas-all.css" type="text/css" />
<link rel="stylesheet" href="css/threshold_styles.css" type="text/css" />
<script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="../nifi/js/jquery/ui-smoothness/jquery-ui-1.10.4.min.js"></script>
<script type="text/javascript" src="../nifi/js/jquery/jquery.center.js"></script>
<script type="text/javascript" src="../nifi/js/jquery/modal/jquery.modal.js"></script>
<script type="text/javascript" src="js/jquery/jqgrid/js/i18n/grid.locale-en.js"></script>
<script type="text/javascript" src="js/jquery/jqgrid/js/jquery.jqGrid.min.js"></script>
<script type="text/javascript" src="js/nf-common.js" ></script>
</head>
<body>
<jsp:include page="/WEB-INF/jsp/error-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/attribute-confirm-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/attribute-add-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/threshold-add-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/threshold-confirm-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/attribute-edit-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/threshold-edit-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/attribute-filter-dialog.jsp"/>
<jsp:include page="/WEB-INF/jsp/threshold-filter-dialog.jsp"/>
<div id="threshold-header-text">Threshold Settings </div>
<div class="list-header" >
<div id="control-box">
<div id="save-cancel">
<div id="attribute-list-header" class="list-header-text">Attributes</div>
<img alt="Separator" src="images/separator.gif">
<input id="attribute-add-main" class="control-style" type="image" src="images/addWorksheetRow.png" title = "Add New Attribute" />
<div id="attribute-filter-values" class="clear-filter"></div>
</div>
</div>
<div id="filter-selection" style="left:20px;">
<table width="80%">
<tr>
<td><div id="attribute-names-header" class="abbreviated-header" >Names</div></td>
</tr>
</table>
</div>
</div>
<div id="list1" class="list_container">
<table id="list"></table>
</div>
<div id="rowdetails"></div>
<div id="status-bar" style="position: relative; bottom: 0px; padding: 2px; height: 15px; width:100%; display: inline-block; background-color: silver; color: black;" ></div>
<div id="faded-background"></div>
<div id="glass-pane"></div>
<div id="attribute-context-menu" class="context-menu">
<div id="attribute-add" class="context-control" ><img src="images/addWorksheetRow.png"/> <span class="context-label">Add Attribute</span></div>
<div id="attribute-edit" class="context-control" ><img style="width:16px; height:16px;" src="../../nifi/images/iconEdit.png"/> <span class="context-label">Edit Attribute</span></div>
<div id="attribute-delete" class="context-control" ><img src="images/removeWorksheetRow.png"/> <span class="context-label">Delete Attribute</span></div>
<div id="attribute-filter" class="context-control" ><img src="images/filter.gif" /> <span class="context-label">Filter Attributes</span></div>
</div>
<div id="threshold-context-menu" class="context-menu">
<div id="threshold-add" class="context-control" ><img src="images/addWorksheetRow.png"/> <span class="context-label">Add Threshold</span></div>
<div id="threshold-edit" class="context-control" ><img style="width:16px; height:16px;" src="../../nifi/images/iconEdit.png"/> <span class="context-label">Edit Threshold</span></div>
<div id="threshold-delete" class="context-control" ><img src="images/removeWorksheetRow.png"/> <span class="context-label">Delete Threshold</span></div>
<div id="threshold-filter" class="context-control" ><img src="images/filter.gif" /> <span class="context-label">Filter Thresholds</span></div>
</div>
</body>
</html>

View File

@ -1,45 +0,0 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<div id="threshold-add-dialog">
<div class="dialog-content" >
<div id="threshold-add-error-message" class="error-message" ></div>
<input type="hidden" id="attribute-row-id"></input>
<div class="setting">
<div class="setting-name">Attribute Value</div>
<div class="setting-field">
<input id="add-attribute-value" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Size Threshold (bytes)</div>
<div class="setting-field">
<input id="add-size-value" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
<div class="setting">
<div class="setting-name">File Threshold</div>
<div class="setting-field">
<input id="add-file-count-value" name="new-property-value" style="width: 200px;" type="text"/>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More