You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2019/11/01 14:41:59 UTC

[nifi] branch master updated: NIFI-6803 - Initial commit for ActionHandler Controller Services

This is an automated email from the ASF dual-hosted git repository.

mattyb149 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b28f6d  NIFI-6803 - Initial commit for ActionHandler Controller Services
5b28f6d is described below

commit 5b28f6dad9ea507dd1419ae4ed4e5fff8032b83d
Author: Yolanda M. Davis <yo...@gmail.com>
AuthorDate: Tue Oct 29 16:21:45 2019 -0400

    NIFI-6803 - Initial commit for ActionHandler Controller Services
    
    NIFI-6803 - updated to description
    
    NIFI-6803 - add Spring reference to Notice
    
    Signed-off-by: Matthew Burgess <ma...@apache.org>
    
    This closes #3856
---
 nifi-assembly/pom.xml                              |   6 +
 .../nifi-rules-action-handler-nar/pom.xml          |  47 ++++
 .../src/main/resources/LICENSE                     | 236 +++++++++++++++++++++
 .../src/main/resources/NOTICE                      |  18 ++
 .../nifi-rules-action-handler-service/pom.xml      |  89 ++++++++
 .../handlers/AbstractActionHandlerService.java     |  37 ++++
 .../nifi/rules/handlers/ActionHandlerLookup.java   | 124 +++++++++++
 .../nifi/rules/handlers/ExpressionHandler.java     | 121 +++++++++++
 .../org/apache/nifi/rules/handlers/LogHandler.java | 159 ++++++++++++++
 .../nifi/rules/handlers/RecordSinkHandler.java     | 144 +++++++++++++
 .../org.apache.nifi.controller.ControllerService   |  18 ++
 .../nifi/rules/handlers/MockComponentLog.java      | 231 ++++++++++++++++++++
 .../rules/handlers/TestActionHandlerLookup.java    | 125 +++++++++++
 .../nifi/rules/handlers/TestExpressionHandler.java | 150 +++++++++++++
 .../apache/nifi/rules/handlers/TestLogHandler.java | 150 +++++++++++++
 .../apache/nifi/rules/handlers/TestProcessor.java  |  46 ++++
 .../nifi/rules/handlers/TestRecordSinkHandler.java | 148 +++++++++++++
 .../nifi-rules-action-handler-bundle/pom.xml       |  43 ++++
 .../nifi/rules/PropertyContextActionHandler.java   |  28 +++
 nifi-nar-bundles/pom.xml                           |   3 +-
 20 files changed, 1922 insertions(+), 1 deletion(-)

diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 50f15aa..3140bab 100755
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -857,6 +857,12 @@ language governing permissions and limitations under the License. -->
                     <version>1.10.0-SNAPSHOT</version>
                     <type>nar</type>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.nifi</groupId>
+                    <artifactId>nifi-rules-action-handler-nar</artifactId>
+                    <version>1.10.0-SNAPSHOT</version>
+                    <type>nar</type>
+                </dependency>
             </dependencies>
         </profile>
         <profile>
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/pom.xml b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/pom.xml
new file mode 100644
index 0000000..b22a195
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+        <artifactId>nifi-rules-action-handler-bundle</artifactId>
+        <groupId>org.apache.nifi</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-rules-action-handler-nar</artifactId>
+    <version>1.10.0-SNAPSHOT</version>
+    <packaging>nar</packaging>
+    <properties>
+        <maven.javadoc.skip>true</maven.javadoc.skip>
+        <source.skip>true</source.skip>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-rules-action-handler-service</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-standard-services-api-nar</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+            <type>nar</type>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/LICENSE b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/LICENSE
new file mode 100644
index 0000000..1f84df0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/LICENSE
@@ -0,0 +1,236 @@
+
+                                 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.
+
+
+nifi-rules-action-handler-nar includes subcomponents with separate copyright notices and
+license terms. Your use of these subcomponents is subject to the terms
+and conditions of the following licenses:
+
+    The binary distribution of this product bundles the 'ASM' library which is available under a BSD style license.
+
+        Copyright (c) 2000-2005 INRIA, France Telecom
+        All rights reserved.
+
+        Redistribution and use in source and binary forms, with or without
+        modification, are permitted provided that the following conditions
+        are met:
+        1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+        2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+        3. Neither the name of the copyright holders nor the names of its
+        contributors may be used to endorse or promote products derived from
+        this software without specific prior written permission.
+
+        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+        ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+        LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+        CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+        SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+        INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+        CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+        ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+        THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/NOTICE b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/NOTICE
new file mode 100644
index 0000000..774e048
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-nar/src/main/resources/NOTICE
@@ -0,0 +1,18 @@
+nifi-rules-action-handler-nar
+Copyright 2014-2019 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+******************
+Apache Software License v2
+******************
+
+The following binary components are provided under the Apache Software License v2
+
+    (ASLv2) MVEL (MVFLEX Expression Language)
+
+    (ASLv2) Spring Framework (Core, Expression)
+    The following NOTICE information applies:
+      Spring Framework
+      Copyright (c) 2002-2019 Pivotal, Inc.
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/pom.xml b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/pom.xml
new file mode 100644
index 0000000..e3e8f75
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+        <artifactId>nifi-rules-action-handler-bundle</artifactId>
+        <groupId>org.apache.nifi</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-rules-action-handler-service</artifactId>
+    <packaging>jar</packaging>
+    <properties>
+        <spring.version>4.3.19.RELEASE</spring.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-rules-engine-service-api</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-record-sink-api</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-record</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-record-serialization-service-api</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-utils</artifactId>
+            <version>1.10.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mvel</groupId>
+            <artifactId>mvel2</artifactId>
+            <version>2.4.4.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-expression</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jeasy</groupId>
+            <artifactId>easy-rules-mvel</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/AbstractActionHandlerService.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/AbstractActionHandlerService.java
new file mode 100644
index 0000000..57bbf28
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/AbstractActionHandlerService.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.rules.PropertyContextActionHandler;
+
+import java.util.Map;
+
+public abstract class AbstractActionHandlerService extends AbstractControllerService implements PropertyContextActionHandler {
+
+    public static enum DebugLevels {
+        trace, debug, info, warn, error
+    }
+    public abstract void execute(Action action, Map<String, Object> facts);
+
+    public void execute(PropertyContext context, Action action, Map<String, Object> facts) {
+        execute(action, facts);
+    }
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ActionHandlerLookup.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ActionHandlerLookup.java
new file mode 100644
index 0000000..2865193
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ActionHandlerLookup.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.nifi.annotation.behavior.DynamicProperty;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnDisabled;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.rules.PropertyContextActionHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Tags({"rules", "rules engine", "action", "action handler","lookup"})
+@CapabilityDescription("Provides an Action Handler that can be used to dynamically select another Action Handler. " +
+"This service will allow multiple ActionHandlers to be defined and registered by action type.  When actions are provided the handlers can " +
+"be dynamically determined and executed at runtime.")
+@DynamicProperty(name = "actionType ", value = "Action Handler Service", expressionLanguageScope = ExpressionLanguageScope.NONE, description = "")
+public class ActionHandlerLookup extends AbstractActionHandlerService{
+
+    private volatile Map<String, PropertyContextActionHandler> actionHandlerMap;
+
+    @Override
+    protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
+        return new PropertyDescriptor.Builder()
+                .name(propertyDescriptorName)
+                .description("The Action handler to return when action type = '" + propertyDescriptorName + "'")
+                .identifiesControllerService(PropertyContextActionHandler.class)
+                .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+                .build();
+    }
+
+    @Override
+    protected Collection<ValidationResult> customValidate(ValidationContext context) {
+        final List<ValidationResult> results = new ArrayList<>();
+
+        int numDefinedServices = 0;
+        for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
+            if (descriptor.isDynamic()) {
+                numDefinedServices++;
+            }
+
+            final String referencedId = context.getProperty(descriptor).getValue();
+            if (this.getIdentifier().equals(referencedId)) {
+                results.add(new ValidationResult.Builder()
+                        .subject(descriptor.getDisplayName())
+                        .explanation("the current service cannot be registered as an ActionHandler to lookup")
+                        .valid(false)
+                        .build());
+            }
+        }
+
+        if (numDefinedServices == 0) {
+            results.add(new ValidationResult.Builder()
+                    .subject(this.getClass().getSimpleName())
+                    .explanation("at least one Action Handler must be defined via dynamic properties")
+                    .valid(false)
+                    .build());
+        }
+
+        return results;
+    }
+
+    @OnEnabled
+    public void onEnabled(final ConfigurationContext context) {
+        final Map<String,PropertyContextActionHandler> serviceMap = new HashMap<>();
+
+        for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
+            if (descriptor.isDynamic()) {
+                final PropertyContextActionHandler propertyContextActionHandler = context.getProperty(descriptor).asControllerService(PropertyContextActionHandler.class);
+                serviceMap.put(descriptor.getName(), propertyContextActionHandler);
+            }
+        }
+
+        actionHandlerMap = Collections.unmodifiableMap(serviceMap);
+    }
+
+    @OnDisabled
+    public void onDisabled() {
+        actionHandlerMap = null;
+    }
+
+    @Override
+    public void execute(Action action, Map<String, Object> facts) {
+        execute(null,action,facts);
+    }
+
+    @Override
+    public void execute(PropertyContext context, Action action, Map<String, Object> facts) {
+        PropertyContextActionHandler actionHandler = actionHandlerMap.get(action.getType());
+        if (actionHandler == null) {
+            throw new ProcessException("No Action Handler was found for Action Type:" + action.getType());
+        }
+        actionHandler.execute(context,action,facts);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ExpressionHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ExpressionHandler.java
new file mode 100644
index 0000000..be41a5c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/ExpressionHandler.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.mvel2.MVEL;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+@Tags({"rules", "rules engine", "action", "action handler", "expression language","MVEL","SpEL"})
+@CapabilityDescription("Executes an action containing an expression written in MVEL or SpEL. The action " +
+"is usually created by a rules engine. ")
+public class ExpressionHandler extends AbstractActionHandlerService {
+
+    enum ExpresssionType {
+        MVEL, SPEL;
+    }
+
+    public static final PropertyDescriptor DEFAULT_EXPRESSION_LANGUAGE_TYPE = new PropertyDescriptor.Builder()
+            .name("Expression Language Type")
+            .required(true)
+            .description("The expression language that should be used to compile and execute action. Supported languages are MVEL and Spring Expression Language (SpEL).")
+            .allowableValues(ExpresssionType.values())
+            .defaultValue("MVEL")
+            .build();
+
+    private List<PropertyDescriptor> properties;
+    private ExpresssionType type;
+
+    @Override
+    protected void init(ControllerServiceInitializationContext config) throws InitializationException {
+        super.init(config);
+        final List<PropertyDescriptor> properties = new ArrayList<>();
+        properties.add(DEFAULT_EXPRESSION_LANGUAGE_TYPE);
+        this.properties = Collections.unmodifiableList(properties);
+    }
+
+    @OnEnabled
+    public void onEnabled(final ConfigurationContext context) throws InitializationException {
+        type = ExpresssionType.valueOf(context.getProperty(DEFAULT_EXPRESSION_LANGUAGE_TYPE).getValue());
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return properties;
+    }
+
+
+    @Override
+    public void execute(Action action, Map<String, Object> facts) {
+        Map<String, String> attributes = action.getAttributes();
+        final String command = attributes.get("command");
+        if(StringUtils.isNotEmpty(command)) {
+            try {
+                final String type = attributes.get("type");
+                ExpresssionType expresssionType = ExpresssionType.valueOf(type);
+                if (expresssionType.equals(ExpresssionType.MVEL)) {
+                    executeMVEL(command, facts);
+                } else {
+                    executeSPEL(command, facts);
+                }
+            } catch (Exception ex) {
+                getLogger().warn("Error occurred when attempting to execute expression. Action: {}, Facts - {}",
+                        new Object[]{action, facts}, ex);
+            }
+        }else{
+            getLogger().warn("Command attribute was not provided.  Action: {}, Facts - {}",
+                    new Object[]{action, facts});
+        }
+    }
+
+    private void executeMVEL(String command, Map<String, Object> facts) {
+        MVEL.executeExpression(MVEL.compileExpression(command), facts);
+        if(getLogger().isDebugEnabled()) {
+            getLogger().debug("Expression was executed successfully: {}: {}", new Object[]{type, command});
+        }
+    }
+
+    private void executeSPEL(String command, Map<String, Object> facts) {
+        final ExpressionParser parser = new SpelExpressionParser();
+        StandardEvaluationContext context = new StandardEvaluationContext();
+        context.setRootObject(facts);
+        context.setVariables(facts);
+        Expression expression = parser.parseExpression(command);
+        Object value = expression.getValue(context);
+        if(getLogger().isDebugEnabled()) {
+            getLogger().debug("Expression was executed successfully with result: {}. {}: {}", new Object[]{value, type, command});
+        }
+    }
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/LogHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/LogHandler.java
new file mode 100644
index 0000000..666afbc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/LogHandler.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogLevel;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Tags({"rules", "rules engine", "action", "action handler", "logging"})
+@CapabilityDescription("Logs messages and fact information based on a provided action (usually created by a rules engine)")
+public class LogHandler extends AbstractActionHandlerService {
+
+    public static final PropertyDescriptor DEFAULT_LOG_LEVEL = new PropertyDescriptor.Builder()
+            .name("Log Level")
+            .required(true)
+            .description("The Log Level to use when logging the Attributes")
+            .allowableValues(DebugLevels.values())
+            .defaultValue("info")
+            .build();
+
+    private static final PropertyDescriptor LOG_FACTS = new PropertyDescriptor.Builder()
+            .name("Log Facts")
+            .required(true)
+            .description("If true, the log message will include the facts which triggered this log action.")
+            .defaultValue("true")
+            .allowableValues("true", "false")
+            .build();
+
+    private static final PropertyDescriptor LOG_PREFIX = new PropertyDescriptor.Builder()
+            .name("Log prefix")
+            .required(false)
+            .description("Log prefix appended to the log lines. It helps to distinguish the output of multiple LogAttribute processors.")
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+            .build();
+
+    private List<PropertyDescriptor> properties;
+    private String logPrefix;
+    private Boolean logFacts;
+    private String defaultLogLevel;
+
+    @Override
+    protected void init(ControllerServiceInitializationContext config) throws InitializationException {
+        super.init(config);
+        final List<PropertyDescriptor> properties = new ArrayList<>();
+        properties.add(LOG_PREFIX);
+        properties.add(LOG_FACTS);
+        properties.add(DEFAULT_LOG_LEVEL);
+        this.properties = Collections.unmodifiableList(properties);
+    }
+
+    @OnEnabled
+    public void onEnabled(final ConfigurationContext context) throws InitializationException {
+        logPrefix = context.getProperty(LOG_PREFIX).evaluateAttributeExpressions().getValue();
+        logFacts = context.getProperty(LOG_FACTS).asBoolean();
+        defaultLogLevel = context.getProperty(DEFAULT_LOG_LEVEL).getValue().toUpperCase();
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return properties;
+    }
+
+    @Override
+    public void execute(Action action, Map<String, Object> facts) {
+        ComponentLog logger = getLogger();
+        Map<String, String> attributes = action.getAttributes();
+        final String logLevel = attributes.get("logLevel");
+        final LogLevel level = getLogLevel(logLevel, LogLevel.valueOf(defaultLogLevel));
+        final String eventMessage = StringUtils.isNotEmpty(attributes.get("message")) ? attributes.get("message") : "Rules Action Triggered Log.";
+        final String factsMessage = createFactsLogMessage(facts, eventMessage);
+        logger.log(level, factsMessage);
+    }
+
+    private LogLevel getLogLevel(String logLevel, LogLevel defaultLevel) {
+        LogLevel level;
+        if (StringUtils.isNotEmpty(logLevel)) {
+            try {
+                level = LogLevel.valueOf(logLevel.toUpperCase());
+            } catch (IllegalArgumentException iea) {
+                level = defaultLevel;
+            }
+        } else {
+            level = defaultLevel;
+        }
+        return level;
+    }
+
+    protected String createFactsLogMessage(Map<String, Object> facts, String eventMessage) {
+
+        final Set<String> fields = facts.keySet();
+        final StringBuilder message = new StringBuilder();
+        String dashedLine;
+
+        if (StringUtils.isBlank(logPrefix)) {
+            dashedLine = StringUtils.repeat('-', 50);
+        } else {
+            // abbreviate long lines
+            logPrefix = StringUtils.abbreviate(logPrefix, 40);
+            // center the logPrefix and pad with dashes
+            logPrefix = StringUtils.center(logPrefix, 40, '-');
+            // five dashes on the left and right side, plus the dashed logPrefix
+            dashedLine = StringUtils.repeat('-', 5) + logPrefix + StringUtils.repeat('-', 5);
+        }
+
+        message.append("\n");
+        message.append(dashedLine);
+        message.append("\n");
+        message.append("Log Message: ");
+        message.append(eventMessage);
+        message.append("\n");
+
+        if (logFacts) {
+            message.append("Log Facts:\n");
+            fields.forEach(field -> {
+                message.append("Field: ");
+                message.append(field);
+                message.append(", Value: ");
+                message.append(facts.get(field));
+                message.append("\n");
+            });
+        }
+
+        return message.toString().trim();
+
+    }
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/RecordSinkHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/RecordSinkHandler.java
new file mode 100644
index 0000000..760315e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/java/org/apache/nifi/rules/handlers/RecordSinkHandler.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.record.sink.RecordSinkService;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.serialization.SimpleRecordSchema;
+import org.apache.nifi.serialization.WriteResult;
+import org.apache.nifi.serialization.record.DataType;
+import org.apache.nifi.serialization.record.ListRecordSet;
+import org.apache.nifi.serialization.record.MapRecord;
+import org.apache.nifi.serialization.record.RecordField;
+import org.apache.nifi.serialization.record.RecordFieldType;
+import org.apache.nifi.serialization.record.RecordSchema;
+import org.apache.nifi.serialization.record.RecordSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Tags({"rules", "rules engine", "action", "action handler", "logging"})
+@CapabilityDescription("Logs messages and fact information based on a provided action (usually created by a rules engine)")
+public class RecordSinkHandler extends AbstractActionHandlerService{
+
+    static final PropertyDescriptor RECORD_SINK_SERVICE = new PropertyDescriptor.Builder()
+            .name("record-sink-service")
+            .displayName("Record Sink Service")
+            .description("Specifies the Controller Service used to support the SEND event action.  If not set SEND events will be ignored.")
+            .identifiesControllerService(RecordSinkService.class)
+            .required(true)
+            .build();
+
+    private RecordSinkService recordSinkService;
+    private List<PropertyDescriptor> properties;
+
+    @Override
+    protected void init(ControllerServiceInitializationContext config) throws InitializationException {
+        super.init(config);
+        final List<PropertyDescriptor> properties = new ArrayList<>();
+        properties.add(RECORD_SINK_SERVICE);
+        this.properties = Collections.unmodifiableList(properties);
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return properties;
+    }
+
+    @OnEnabled
+    public void onEnabled(final ConfigurationContext context) throws InitializationException {
+        if(context.getProperty(RECORD_SINK_SERVICE).isSet()) {
+            recordSinkService = context.getProperty(RECORD_SINK_SERVICE).asControllerService(RecordSinkService.class);
+        }
+    }
+
+    @Override
+    public void execute(Action action, Map<String, Object> facts) {
+        Map<String, String> attributes = action.getAttributes();
+        boolean sendZeroResults = attributes.containsKey("sentZeroResults") && Boolean.parseBoolean(attributes.get("sendZeroResults"));
+        final RecordSet recordSet = getRecordSet(facts);
+
+        try {
+            WriteResult result = recordSinkService.sendData(recordSet, attributes, sendZeroResults);
+            if(getLogger().isDebugEnabled() && result != null){
+                getLogger().debug("Records written to sink service: {}", new Object[]{result.getRecordCount()});
+            }
+        }catch (Exception ex){
+            getLogger().warn("Exception encountered when attempting to send metrics", ex);
+        }
+    }
+
+    private RecordSet getRecordSet(Map<String, Object> metrics){
+        List<RecordField> recordFields = metrics.entrySet().stream().map(entry ->
+                new RecordField(entry.getKey(),getDataType(String.valueOf(entry.getValue())))
+        ).collect(Collectors.toList());
+        final RecordSchema recordSchema = new SimpleRecordSchema(recordFields);
+        return new ListRecordSet(recordSchema, Arrays.asList( new MapRecord(recordSchema,metrics)));
+    }
+
+    private DataType getDataType(final String value) {
+        if (value == null || value.isEmpty()) {
+            return null;
+        }
+
+        if (NumberUtils.isParsable(value)) {
+            if (value.contains(".")) {
+                try {
+                    final double doubleValue = Double.parseDouble(value);
+                    if (doubleValue > Float.MAX_VALUE || doubleValue < Float.MIN_VALUE) {
+                        return RecordFieldType.DOUBLE.getDataType();
+                    }
+
+                    return RecordFieldType.FLOAT.getDataType();
+                } catch (final NumberFormatException nfe) {
+                    return RecordFieldType.STRING.getDataType();
+                }
+            }
+
+            try {
+                final long longValue = Long.parseLong(value);
+                if (longValue > Integer.MAX_VALUE || longValue < Integer.MIN_VALUE) {
+                    return RecordFieldType.LONG.getDataType();
+                }
+
+                return RecordFieldType.INT.getDataType();
+            } catch (final NumberFormatException nfe) {
+                return RecordFieldType.STRING.getDataType();
+            }
+        }
+
+        if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+            return RecordFieldType.BOOLEAN.getDataType();
+        }
+
+        return RecordFieldType.STRING.getDataType();
+
+    }
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
new file mode 100644
index 0000000..7d2967f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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.rules.handlers.ActionHandlerLookup
+org.apache.nifi.rules.handlers.ExpressionHandler
+org.apache.nifi.rules.handlers.LogHandler
+org.apache.nifi.rules.handlers.RecordSinkHandler
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/MockComponentLog.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/MockComponentLog.java
new file mode 100644
index 0000000..9d5781f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/MockComponentLog.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogLevel;
+
+public class MockComponentLog implements ComponentLog {
+
+    String infoMessage;
+    String warnMessage;
+    String debugMessage;
+    String traceMessage;
+    String errorMessage;
+
+    protected String convertMessage(String msg, Object[] os){
+        String replaceMsg = msg;
+        for (Object o : os) {
+            replaceMsg = replaceMsg.replaceFirst("\\{\\}", os.toString());
+        }
+        return replaceMsg;
+    }
+
+    @Override
+    public void warn(String msg, Throwable t) {
+        warn(msg);
+    }
+
+    @Override
+    public void warn(String msg, Object[] os) {
+        warn(msg);
+    }
+
+    @Override
+    public void warn(String msg, Object[] os, Throwable t) {
+        warn(convertMessage(msg,os));
+    }
+
+    @Override
+    public void warn(String msg) {
+        warnMessage = msg;
+    }
+
+    @Override
+    public void trace(String msg, Throwable t) {
+        trace(msg);
+    }
+
+    @Override
+    public void trace(String msg, Object[] os) {
+        trace(msg);
+    }
+
+    @Override
+    public void trace(String msg) {
+        traceMessage = msg;
+    }
+
+    @Override
+    public void trace(String msg, Object[] os, Throwable t) {
+        trace(convertMessage(msg,os));
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return true;
+    }
+
+    @Override
+    public void info(String msg, Throwable t) {
+        info(msg);
+    }
+
+    @Override
+    public void info(String msg, Object[] os) {
+        info(msg);
+    }
+
+    @Override
+    public void info(String msg) {
+        infoMessage = msg;
+    }
+
+    @Override
+    public void info(String msg, Object[] os, Throwable t) {
+        info(convertMessage(msg,os));
+    }
+
+    @Override
+    public String getName() {
+        return null;
+    }
+
+    @Override
+    public void error(String msg, Throwable t) {
+        error(msg);
+    }
+
+    @Override
+    public void error(String msg, Object[] os) {
+        error(convertMessage(msg,os));
+    }
+
+    @Override
+    public void error(String msg) {
+        errorMessage = msg;
+    }
+
+    @Override
+    public void error(String msg, Object[] os, Throwable t) {
+        error(msg);
+    }
+
+    @Override
+    public void debug(String msg, Throwable t) {
+        debug(msg);
+    }
+
+    @Override
+    public void debug(String msg, Object[] os) {
+        debug(convertMessage(msg,os));
+    }
+
+    @Override
+    public void debug(String msg, Object[] os, Throwable t) {
+        debug(msg);
+    }
+
+    @Override
+    public void debug(String msg) {
+        debugMessage = msg;
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Throwable t) {
+
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Object[] os) {
+
+    }
+
+    @Override
+    public void log(LogLevel level, String msg) {
+        switch (level) {
+            case WARN:
+                warn(msg);
+                break;
+            case DEBUG:
+                debug(msg);
+                break;
+            case INFO:
+                info(msg);
+                break;
+            case ERROR:
+                error(msg);
+                break;
+            case TRACE:
+                trace(msg);
+                break;
+            case FATAL:
+                error(msg);
+                break;
+            case NONE:
+                info(msg);
+                break;
+        }
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Object[] os, Throwable t) {
+
+    }
+
+    public String getInfoMessage() {
+        return infoMessage;
+    }
+
+    public String getWarnMessage() {
+        return warnMessage;
+    }
+
+    public String getDebugMessage() {
+        return debugMessage;
+    }
+
+    public String getTraceMessage() {
+        return traceMessage;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestActionHandlerLookup.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestActionHandlerLookup.java
new file mode 100644
index 0000000..f9087ae
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestActionHandlerLookup.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class TestActionHandlerLookup {
+
+    private MockPropertyActionHandler alertHandler;
+    private MockPropertyActionHandler logHandler;
+    private ActionHandlerLookup actionHandlerLookup;
+    private TestRunner runner;
+
+    @Before
+    public void setup() throws InitializationException {
+        alertHandler = new MockPropertyActionHandler();
+        logHandler = new MockPropertyActionHandler();
+        actionHandlerLookup = new ActionHandlerLookup();
+
+        runner = TestRunners.newTestRunner(TestProcessor.class);
+
+        final String alertIdentifier = "alert-handler";
+        runner.addControllerService(alertIdentifier, alertHandler);
+
+        final String logIdentifier = "log-handler";
+        runner.addControllerService(logIdentifier, logHandler);
+
+        runner.addControllerService("action-handler-lookup", actionHandlerLookup);
+        runner.setProperty(actionHandlerLookup, "ALERT", alertIdentifier);
+        runner.setProperty(actionHandlerLookup, "LOG", logIdentifier);
+
+        runner.enableControllerService(alertHandler);
+        runner.enableControllerService(logHandler);
+        runner.enableControllerService(actionHandlerLookup);
+
+    }
+
+    @Test
+    public void testLookupAlert() {
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+        attributes.put("logLevel", "INFO");
+        attributes.put("message", "This should be not sent as an alert!");
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+        final Action action = new Action();
+        action.setType("ALERT");
+        action.setAttributes(attributes);
+        actionHandlerLookup.execute(null, action, metrics);
+        assert alertHandler.getExecuteContextCalled();
+    }
+
+    @Test
+    public void testLookupLog() {
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+        attributes.put("logLevel", "INFO");
+        attributes.put("message", "This should be not sent as an alert!");
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+        final Action action = new Action();
+        action.setType("LOG");
+        action.setAttributes(attributes);
+        actionHandlerLookup.execute(null, action, metrics);
+        assert logHandler.getExecuteContextCalled();
+    }
+
+    @Test(expected = ProcessException.class)
+    public void testLookupInvalidActionType() {
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+        attributes.put("logLevel", "INFO");
+        attributes.put("message", "This should be not sent as an alert!");
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+        final Action action = new Action();
+        action.setType("FAKE");
+        actionHandlerLookup.execute(null,action,metrics);
+    }
+
+    private static class MockPropertyActionHandler extends AbstractActionHandlerService  {
+
+        Boolean executeContextCalled = false;
+
+        @Override
+        public void execute(Action action, Map<String, Object> facts) {
+            execute(null,action,facts);
+        }
+
+        @Override
+        public void execute(PropertyContext context, Action action, Map<String, Object> facts) {
+            executeContextCalled = true;
+        }
+
+        public Boolean getExecuteContextCalled() {
+            return executeContextCalled;
+        }
+    }
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestExpressionHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestExpressionHandler.java
new file mode 100644
index 0000000..10cc6b3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestExpressionHandler.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertThat;
+
+public class TestExpressionHandler {
+
+    private TestRunner runner;
+    private MockComponentLog mockComponentLog;
+    private ExpressionHandler expressionHandler;
+
+    @Before
+    public void setup() throws InitializationException {
+        runner = TestRunners.newTestRunner(TestProcessor.class);
+        mockComponentLog = new MockComponentLog();
+        ExpressionHandler handler = new MockExpressionHandler(mockComponentLog);
+        runner.addControllerService("MockExpressionHandler", handler);
+        runner.enableControllerService(handler);
+        expressionHandler = (ExpressionHandler) runner.getProcessContext()
+                .getControllerServiceLookup()
+                .getControllerService("MockExpressionHandler");
+    }
+
+    @Test
+    public void testValidService() {
+        runner.assertValid(expressionHandler);
+        assertThat(expressionHandler, instanceOf(ExpressionHandler.class));
+    }
+
+    @Test
+    public void testMvelExpression(){
+
+        final Map<String,String> attributes = new HashMap<>();
+        final Map<String,Object> metrics = new HashMap<>();
+        final String expectedMessage = "Expression was executed successfully:";
+
+        attributes.put("command","System.out.println(jvmHeap)");
+        attributes.put("type","MVEL");
+        metrics.put("jvmHeap","1000000");
+        metrics.put("cpu","90");
+
+        final Action action = new Action();
+        action.setType("EXPRESSION");
+        action.setAttributes(attributes);
+        expressionHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getDebugMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertTrue(logMessage.startsWith(expectedMessage));
+    }
+
+    @Test
+    public void testSpelExpression(){
+        final Map<String,String> attributes = new HashMap<>();
+        final Map<String,Object> metrics = new HashMap<>();
+        final String expectedMessage = "Expression was executed successfully with result:";
+
+        attributes.put("command","#jvmHeap + ' is large'");
+        attributes.put("type","SPEL");
+        metrics.put("jvmHeap","1000000");
+        metrics.put("cpu","90");
+
+        final Action action = new Action();
+        action.setType("EXPRESSION");
+        action.setAttributes(attributes);
+        expressionHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getDebugMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertTrue(logMessage.startsWith(expectedMessage));
+    }
+
+    @Test
+    public void testInvalidType() {
+        final Map<String,String> attributes = new HashMap<>();
+        final Map<String,Object> metrics = new HashMap<>();
+        final String expectedMessage = "Error occurred when attempting to execute expression.";
+
+        attributes.put("command","#jvmHeap + ' is large'");
+        attributes.put("type","FAKE");
+        metrics.put("jvmHeap","1000000");
+        metrics.put("cpu","90");
+
+        final Action action = new Action();
+        action.setType("EXPRESSION");
+        action.setAttributes(attributes);
+        expressionHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getWarnMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertTrue(logMessage.startsWith(expectedMessage));
+    }
+
+    @Test
+    public void testNoCommandProvided() {
+        final Map<String,String> attributes = new HashMap<>();
+        final Map<String,Object> metrics = new HashMap<>();
+        final String expectedMessage = "Command attribute was not provided.";
+        attributes.put("type","FAKE");
+        metrics.put("jvmHeap","1000000");
+        metrics.put("cpu","90");
+
+        final Action action = new Action();
+        action.setType("EXPRESSION");
+        action.setAttributes(attributes);
+        expressionHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getWarnMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertTrue(logMessage.startsWith(expectedMessage));
+    }
+
+    private static class MockExpressionHandler extends ExpressionHandler{
+        private ComponentLog testLogger;
+
+        public MockExpressionHandler(ComponentLog testLogger) {
+            this.testLogger = testLogger;
+        }
+
+        @Override
+        protected ComponentLog getLogger() {
+            return testLogger;
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestLogHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestLogHandler.java
new file mode 100644
index 0000000..ec20de7
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestLogHandler.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class TestLogHandler {
+
+    TestRunner runner;
+    MockComponentLog mockComponentLog;
+    LogHandler logHandler;
+
+    @Before
+    public void setup() throws InitializationException {
+        runner = TestRunners.newTestRunner(TestProcessor.class);
+        mockComponentLog = new MockComponentLog();
+        LogHandler handler = new MockLogHandler(mockComponentLog);
+        runner.addControllerService("MockLogHandler", handler);
+        runner.enableControllerService(handler);
+        logHandler = (LogHandler) runner.getProcessContext()
+                .getControllerServiceLookup()
+                .getControllerService("MockLogHandler");
+
+    }
+
+    @Test
+    public void testValidService() {
+        runner.assertValid(logHandler);
+        assertThat(logHandler, instanceOf(LogHandler.class));
+    }
+
+    @Test
+    public void testWarningLogged() {
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+
+        final String expectedMessage = "--------------------------------------------------\n" +
+                "Log Message: This is a warning\n" +
+                "Log Facts:\n" +
+                "Field: cpu, Value: 90\n" +
+                "Field: jvmHeap, Value: 1000000";
+
+
+        attributes.put("logLevel", "warn");
+        attributes.put("message", "This is a warning");
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+        final Action action = new Action();
+        action.setType("LOG");
+        action.setAttributes(attributes);
+        logHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getWarnMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertEquals(expectedMessage, logMessage);
+    }
+
+    @Test
+    public void testNoLogAttributesProvided() {
+
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+        final String expectedMessage = "--------------------------------------------------\n" +
+                "Log Message: Rules Action Triggered Log.\n" +
+                "Log Facts:\n" +
+                "Field: cpu, Value: 90\n" +
+                "Field: jvmHeap, Value: 1000000";
+
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+
+        final Action action = new Action();
+        action.setType("LOG");
+        action.setAttributes(attributes);
+        logHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getInfoMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertEquals(expectedMessage, logMessage);
+
+    }
+
+    @Test
+    public void testInvalidLogLevelProvided() {
+        final Map<String, String> attributes = new HashMap<>();
+        final Map<String, Object> metrics = new HashMap<>();
+
+        attributes.put("logLevel", "FAKE");
+
+        final String expectedMessage = "--------------------------------------------------\n" +
+                "Log Message: Rules Action Triggered Log.\n" +
+                "Log Facts:\n" +
+                "Field: cpu, Value: 90\n" +
+                "Field: jvmHeap, Value: 1000000";
+
+        metrics.put("jvmHeap", "1000000");
+        metrics.put("cpu", "90");
+
+        final Action action = new Action();
+        action.setType("LOG");
+        action.setAttributes(attributes);
+        logHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getInfoMessage();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertEquals(expectedMessage, logMessage);
+
+    }
+
+    private static class MockLogHandler extends LogHandler {
+        private ComponentLog testLogger;
+
+        public MockLogHandler(ComponentLog testLogger) {
+            this.testLogger = testLogger;
+        }
+
+        @Override
+        protected ComponentLog getLogger() {
+            return testLogger;
+        }
+    }
+
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestProcessor.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestProcessor.java
new file mode 100644
index 0000000..43ad6ff
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestProcessor.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.handlers;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.exception.ProcessException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestProcessor extends AbstractProcessor {
+
+    @Override
+    public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException {
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        List<PropertyDescriptor> properties = new ArrayList<>();
+        properties.add(new PropertyDescriptor.Builder()
+                .name("log-action-handler-test")
+                .description("Logging Action Handler")
+                .identifiesControllerService(LogHandler.class)
+                .required(true)
+                .build());
+        return properties;
+    }
+
+}
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestRecordSinkHandler.java b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestRecordSinkHandler.java
new file mode 100644
index 0000000..8566a2a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/nifi-rules-action-handler-service/src/test/java/org/apache/nifi/rules/handlers/TestRecordSinkHandler.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.rules.handlers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.components.AbstractConfigurableComponent;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.record.sink.RecordSinkService;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.serialization.WriteResult;
+import org.apache.nifi.serialization.record.Record;
+import org.apache.nifi.serialization.record.RecordSchema;
+import org.apache.nifi.serialization.record.RecordSet;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+public class TestRecordSinkHandler {
+    private TestRunner runner;
+    private MockComponentLog mockComponentLog;
+    private RecordSinkHandler recordSinkHandler;
+    private MockRecordSinkService recordSinkService;
+
+    @Before
+    public void setup() throws InitializationException {
+        runner = TestRunners.newTestRunner(TestProcessor.class);
+        mockComponentLog = new MockComponentLog();
+        RecordSinkHandler handler = new MockRecordSinkHandler(mockComponentLog);
+        recordSinkService = new MockRecordSinkService();
+        runner.addControllerService("MockRecordSinkService", recordSinkService);
+        runner.enableControllerService(recordSinkService);
+        runner.addControllerService("MockRecordSinkHandler", handler);
+        runner.setProperty(handler, MockRecordSinkHandler.RECORD_SINK_SERVICE,"MockRecordSinkService");
+        runner.enableControllerService(handler);
+        recordSinkHandler = (RecordSinkHandler) runner.getProcessContext()
+                .getControllerServiceLookup()
+                .getControllerService("MockRecordSinkHandler");
+    }
+
+    @Test
+    public void testValidService() {
+        runner.assertValid(recordSinkHandler);
+        assertThat(recordSinkHandler, instanceOf(RecordSinkHandler.class));
+    }
+
+    @Test
+    public void testRecordSendViaSink() throws InitializationException, IOException {
+        final Map<String,String> attributes = new HashMap<>();
+        final Map<String,Object> metrics = new HashMap<>();
+        final String expectedMessage = "Records written to sink service:";
+
+        attributes.put("sendZeroResults","false");
+        metrics.put("jvmHeap","1000000");
+        metrics.put("cpu","90");
+
+        final Action action = new Action();
+        action.setType("SEND");
+        action.setAttributes(attributes);
+        recordSinkHandler.execute(action, metrics);
+        String logMessage = mockComponentLog.getDebugMessage();
+        List<Map<String, Object>> rows = recordSinkService.getRows();
+        assertTrue(StringUtils.isNotEmpty(logMessage));
+        assertTrue(logMessage.startsWith(expectedMessage));
+        assertFalse(rows.isEmpty());
+        Map<String,Object> record = rows.get(0);
+        assertEquals("90", (record.get("cpu")));
+        assertEquals("1000000", (record.get("jvmHeap")));
+    }
+
+    private static class MockRecordSinkHandler extends RecordSinkHandler {
+        private ComponentLog testLogger;
+
+        public MockRecordSinkHandler(ComponentLog testLogger) {
+            this.testLogger = testLogger;
+        }
+
+        @Override
+        protected ComponentLog getLogger() {
+            return testLogger;
+        }
+    }
+
+    private static class MockRecordSinkService extends AbstractConfigurableComponent implements RecordSinkService {
+
+        private List<Map<String, Object>> rows = new ArrayList<>();
+
+        @Override
+        public WriteResult sendData(RecordSet recordSet, Map<String,String> attributes, boolean sendZeroResults) throws IOException {
+            int numRecordsWritten = 0;
+            RecordSchema recordSchema = recordSet.getSchema();
+            Record record;
+            while ((record = recordSet.next()) != null) {
+                Map<String, Object> row = new HashMap<>();
+                final Record finalRecord = record;
+                recordSchema.getFieldNames().forEach((fieldName) -> row.put(fieldName, finalRecord.getValue(fieldName)));
+                rows.add(row);
+                numRecordsWritten++;
+            }
+            return WriteResult.of(numRecordsWritten, Collections.emptyMap());
+        }
+
+        @Override
+        public String getIdentifier() {
+            return "MockRecordSinkService";
+        }
+
+        @Override
+        public void initialize(ControllerServiceInitializationContext context) throws InitializationException {
+        }
+
+        public List<Map<String, Object>> getRows() {
+            return rows;
+        }
+    }
+
+
+}
diff --git a/nifi-nar-bundles/nifi-rules-action-handler-bundle/pom.xml b/nifi-nar-bundles/nifi-rules-action-handler-bundle/pom.xml
new file mode 100644
index 0000000..4bbf82e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-rules-action-handler-bundle/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>
+        <artifactId>nifi-nar-bundles</artifactId>
+        <groupId>org.apache.nifi</groupId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-rules-action-handler-bundle</artifactId>
+    <packaging>pom</packaging>
+    <modules>
+        <module>nifi-rules-action-handler-nar</module>
+        <module>nifi-rules-action-handler-service</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.nifi</groupId>
+                <artifactId>nifi-rules-action-handler-service</artifactId>
+                <version>1.10.0-SNAPSHOT</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+</project>
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/PropertyContextActionHandler.java b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/PropertyContextActionHandler.java
new file mode 100644
index 0000000..c82beee
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/PropertyContextActionHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules;
+
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.controller.ControllerService;
+
+import java.util.Map;
+
+public interface PropertyContextActionHandler extends ActionHandler, ControllerService {
+
+    void execute(PropertyContext context, Action action, Map<String, Object> facts);
+
+}
diff --git a/nifi-nar-bundles/pom.xml b/nifi-nar-bundles/pom.xml
index 7b967ca..00ad72a 100755
--- a/nifi-nar-bundles/pom.xml
+++ b/nifi-nar-bundles/pom.xml
@@ -99,7 +99,8 @@
         <module>nifi-prometheus-bundle</module>
         <module>nifi-easyrules-bundle</module>
         <module>nifi-sql-reporting-bundle</module>
-  </modules>
+        <module>nifi-rules-action-handler-bundle</module>
+    </modules>
 
     <build>
         <plugins>