http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/docs/src/site/twiki/DG_CustomActionExecutor.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/DG_CustomActionExecutor.twiki 
b/docs/src/site/twiki/DG_CustomActionExecutor.twiki
index 4acbf0d..7831484 100644
--- a/docs/src/site/twiki/DG_CustomActionExecutor.twiki
+++ b/docs/src/site/twiki/DG_CustomActionExecutor.twiki
@@ -66,6 +66,9 @@ Any configuration properties to be made available to this 
class should also be a
 
 The XML schema (XSD) for the new Actions should be added to oozie-site.xml, 
under the property 'oozie.service.WorkflowSchemaService.ext.schemas'. A comma 
separated list for multiple Action schemas.
 
+The XML schema (XSD) for the new action should be also added to Fluent Job 
API. Please refer to
+[[DG_FluentJobAPI#AE.C_Appendix_C_How_To_Extend][Fluent Job API :: How To 
Extend]] for details.
+
 The executor class should be placed along with the oozie webapp in the correct 
path. Once Oozie is restarted, the custom action node can be used in workflows.
 
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/docs/src/site/twiki/DG_Examples.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/DG_Examples.twiki 
b/docs/src/site/twiki/DG_Examples.twiki
index 13dfa28..5323a17 100644
--- a/docs/src/site/twiki/DG_Examples.twiki
+++ b/docs/src/site/twiki/DG_Examples.twiki
@@ -186,6 +186,13 @@ import java.util.Properties;
 Also asynchronous actions like FS action can be used / tested using 
=LocalOozie= / =OozieClient= API. Please see the module
 =oozie-mini= for details like =fs-decision.xml= workflow example.
 
+
+---++ Fluent Job API Examples
+
+There are some elaborate examples how to use the [[DG_FluentJobAPI][Fluent Job 
API]], under =examples/fluentjob/=. There are two
+simple examples covered under [[DG_FluentJobAPI#A_Simple_Example][Fluent Job 
API :: A Simple Example]] and
+[[DG_FluentJobAPI#A_More_Verbose_Example][Fluent Job API :: A More Verbose 
Example]].
+
 [[index][::Go back to Oozie Documentation Index::]]
 
 </noautolink>

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/docs/src/site/twiki/DG_FluentJobAPI.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/DG_FluentJobAPI.twiki 
b/docs/src/site/twiki/DG_FluentJobAPI.twiki
new file mode 100644
index 0000000..c8b764b
--- /dev/null
+++ b/docs/src/site/twiki/DG_FluentJobAPI.twiki
@@ -0,0 +1,376 @@
+<noautolink>
+
+[[index][::Go back to Oozie Documentation Index::]]
+
+---+!! Fluent Job API
+
+%TOC%
+
+---++ Introduction
+
+Oozie is a mature workflow scheduler system. XML is the standard way of 
defining workflow, coordinator, or bundle jobs.  For users
+who prefer an alternative, the Fluent Job API provides a Java interface 
instead.
+
+---+++ Motivation
+
+Prior to Oozie 5.1.0, the following ways were available to submit a workflow, 
coordinator, or bundle job: through Oozie CLI or via
+HTTP submit a generic workflow, coordinator, or bundle job, or submit a Pig, 
Hive, Sqoop, or MapReduce workflow job.
+
+As the generic way goes, the user has to have uploaded a workflow, 
coordinator, or bundle XML and all necessary dependencies like
+scripts, JAR or ZIP files, to HDFS beforehand, as well as have a 
=job.properties= file at command line and / or provide any
+missing parameters as part of the command.
+
+As the specific Pig, Hive, or Sqoop ways go, the user can provide all 
necessary parameters as part of the command issued. A
+ =workflow.xml= file will be generated with all the necessary details and 
stored to HDFS so that Oozie can grab it. Note that
+dependencies have to be uploaded to HDFS beforehand as well.
+
+There are some usability problems by using the XML job definition. XML is not 
an ideal way to express dependencies and a directed
+acyclic graph (DAG). We have to define a control flow, that is, which action 
follows the actual one. It's also necessary to build
+the whole control flow up front as XML is a declarative language that doesn't 
allow for dynamic evaluation. We have to define also
+boilerplate actions like start and end - those are present in every Oozie 
workflow, still need to explicitly define these.
+
+Apart from boilerplate actions, all the transitions between actions have also 
to be defined and taken care of. Furthermore, multiple
+similar actions cannot inherit common properties from each other. Again, the 
reason being workflows are defined in XML.
+
+Fork and join actions have to be defined in pairs, that is, there shouldn't be 
defined a join those incoming actions do not share
+the same ancestor fork. Such situations would result still in a DAG, but Oozie 
doesn't currently allow that. Note that with Fluent
+Job API new dependencies are introduced automatically when the DAG represented 
by API code couldn't have been expressed as
+fork / join pairs automatically.
+
+Either way, there were no programmatic ways to define workflow jobs. That 
doesn't mean users could not generate XML themselves -
+actually this is something HUE's Oozie UI also tries to target.
+
+---+++ Goals
+
+Fluent Job API aims to solve following from the user's perspective. It 
provides a Java API instead of declarative XML to define
+workflows. It defines dependencies across actions as opposed to defining a 
control flow. This is how data engineers and data
+scientists think. It eliminates all boilerplate actions and transitions. Only 
the necessary bits should be defined.
+
+Multiple similar actions can inherit from each other. In fact, since Fluent 
Job API is programmatic, it's possible to generate
+actions or even workflows using conditional, iterative, or recursive 
structures.
+
+Fluent Job API is backwards compatible with workflows defined as XML. That is, 
it should also be possible to have a Fluent Job API
+workflow rendered as XML, as well as coexist XML based and Fluent Job API 
based workflows in the same Oozie installation at the same
+time all workflow action types. When XSDs change, as few manual steps are 
necessary as possible both on API internal and public
+side.
+
+---+++ Non-goals
+
+The following points are not targeted for the initial release of Fluent Job 
API with Oozie 5.1.0. It doesn't provide API in any
+language other than Java. It doesn't provide a REPL. It doesn't allow for 
dynamic action instantiation depending on e.g. conditional
+logic. That is, using the API users still have to implement the whole workflow 
generation logic in advance.
+
+It has no support for programmatic coordinators and bundles, or even EL 
expressions created by API builders. Note that EL
+expressions for workflows can now be expressed the way these are used in XML 
workflow definitions, as strings in the right places.
+
+At the moment only the transformation from Fluent Job API to workflow 
definition is present. The other direction, from workflow
+definition to Fluent Job API JAR artifact, though sensible, is not supported.
+
+It's based only on latest XSDs. Older XSD versions, as well as conversion 
between XSD versions are not supported. Also no support
+for user-supplied custom actions / XSDs.
+
+Most of the non-goals may be targeted as enhancements of the Fluent Job API 
for future Oozie releases.
+
+---+++ Approach
+
+When using the Fluent Job API, the following points are different from the XML 
jobs definition. Instead of control flow (successor)
+definition, the user can define dependencies (parents of an action).
+
+All boilerplate (start, end, ...) has been eliminated, only nodes having 
useful actions have to be defined.
+
+Control flow and necessary boilerplate are generated automatically by keeping 
user defined dependencies, and possibly introducing
+new dependencies to keep Oozie workflow format of nested fork / join pairs. 
Note that not every dependency DAG can be expressed in
+the Oozie workflow format. When this is not possible, user is notified at 
build time.
+
+---++ How To Use
+
+---+++ A Simple Example
+
+The simplest thing to create using the Oozie Fluent Job API is a workflow 
consisting of only one action. Let's see how it goes, step
+by step.
+
+First, put the project =org.apache.oozie:oozie-fluent-job-api= to the build 
path. In case of a Maven managed build, create a new
+Maven project and declare a Maven dependency to 
=org.apache.oozie:oozie-fluent-job-api=.
+
+Then, create a class that =implements WorkflowFactory= and implement the 
method =WorkflowFactory#create()=. inside that method,
+create a =ShellAction= using =ShellActionBuilder=, fill in some attributes 
then create a =Workflow= using =WorkflowBuilder= using
+the =ShellAction= just built. Return the =Workflow=.
+
+Compile a Fluent Job API jar that has the =Main-Class= attribute set to the 
=WorkflowFactory= subclass just created,
+e.g. =shell-workflow.jar=.
+
+Moving on, 
[[DG_CommandLineTool#Checking_a_workflow_definition_generated_by_a_Fluent_Job_API_jar_file][check
 via command line]] that
+the compiled API JAR file is valid.
+
+As a finishing touch,
+[[DG_CommandLineTool#Running_a_workflow_definition_generated_by_a_Fluent_Job_API_jar_file][run
 via command line]] the Fluent Job API
+workflow.
+
+*For reference, a simplistic API JAR example consisting of a =Workflow= having 
only one =ShellAction=:*
+<verbatim>
+public class MyFirstWorkflowFactory implements WorkflowFactory {
+.
+    @Override
+    public Workflow create() {
+        final ShellAction shellAction = ShellActionBuilder.create()
+                .withName("shell-action")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                .withExecutable("echo")
+                .withArgument("my_output=Hello Oozie")
+                .withCaptureOutput(true)
+                .build();
+.
+        final Workflow shellWorkflow = new WorkflowBuilder()
+                .withName("shell-workflow")
+                .withDagContainingNode(shellAction).build();
+.
+        return shellWorkflow;
+    }
+}
+</verbatim>
+
+*After check, the generated workflow XML looks like this:*
+<verbatim>
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<workflow:workflow-app xmlns:workflow="uri:oozie:workflow:1.0"  
xmlns:shell="uri:oozie:shell-action:1.0" name="shell-workflow">
+.
+    <workflow:start to="parent"/>
+.
+    <workflow:kill name="kill">
+        <workflow:message>Action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]</workflow:message>
+    </workflow:kill>
+.
+    <workflow:action name="shell-action">
+        <shell:shell>
+            <shell:resource-manager>${resourceManager}</shell:resource-manager>
+            <shell:name-node>${nameNode}</shell:name-node>
+            <shell:configuration>
+                <shell:property>
+                    <shell:name>mapred.job.queue.name</shell:name>
+                    <shell:value>${queueName}</shell:value>
+                </shell:property>
+            </shell:configuration>
+            <shell:exec>echo</shell:exec>
+            <shell:argument>my_output=Hello Oozie</shell:argument>
+            <shell:capture-output/>
+        </shell:shell>
+        <workflow:ok to="end"/>
+        <workflow:error to="kill"/>
+    </workflow:action>
+.
+    <workflow:end name="end"/>
+.
+</workflow:workflow-app>
+</verbatim>
+
+
+---+++ A More Verbose Example
+
+*Error handling*
+
+If you would like to provide some error handling in case of action failure, 
you should add an =ErrorHandler= to the =Node=
+representing the action. The error handler action will be added as the 
="error-transition"= of the original action in the generated
+Oozie workflow XML. Both the ="ok-transition"= and the ="error-transition"= of 
the error handler action itself will lead to an
+autogenerated kill node.
+
+*Here you find an example consisting of a =Workflow= having three 
=ShellAction=s, an error handler =EmailAction=, and one =decision=
+to sort out which way to go:*
+<verbatim>
+public class MySecondWorkflowFactory implements WorkflowFactory {
+.
+    @Override
+    public Workflow create() {
+        final ShellAction parent = ShellActionBuilder.create()
+                .withName("parent")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                .withExecutable("echo")
+                .withArgument("my_output=Hello Oozie")
+                .withCaptureOutput(true)
+                
.withErrorHandler(ErrorHandler.buildAsErrorHandler(EmailActionBuilder.create()
+                        .withName("email-on-error")
+                        .withRecipient("[email protected]")
+                        .withSubject("Workflow error")
+                        .withBody("Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]")))
+                .build();
+.
+        ShellActionBuilder.createFromExistingAction(parent)
+                .withName("happy-path")
+                .withParentWithCondition(parent, 
"${wf:actionData('parent')['my_output'] eq 'Hello Oozie'}")
+                .withoutArgument("my_output=Hello Oozie")
+                .withArgument("Happy path")
+                .withCaptureOutput(null)
+                .build();
+.
+        ShellActionBuilder.createFromExistingAction(parent)
+                .withName("sad-path")
+                .withParentDefaultConditional(parent)
+                .withArgument("Sad path")
+                .build();
+.
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("shell-example")
+                .withDagContainingNode(parent).build();
+.
+        return workflow;
+    }
+}
+</verbatim>
+
+*After check, the generated workflow XML looks like this:*
+<verbatim>
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<workflow:workflow-app ... name="shell-example">
+.
+    <workflow:start to="parent"/>
+.
+    <workflow:kill name="kill">
+        <workflow:message>Action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]</workflow:message>
+    </workflow:kill>
+.
+    <workflow:action name="email-on-error">
+        <email:email>
+            <email:to>[email protected]</email:to>
+            <email:subject>Workflow error</email:subject>
+            <email:body>Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]</email:body>
+        </email:email>
+        <workflow:ok to="kill"/>
+        <workflow:error to="kill"/>
+    </workflow:action>
+.
+    <workflow:action name="parent">
+        <shell:shell>
+            <shell:resource-manager>${resourceManager}</shell:resource-manager>
+            <shell:name-node>${nameNode}</shell:name-node>
+            <shell:configuration>
+                <shell:property>
+                    <shell:name>mapred.job.queue.name</shell:name>
+                    <shell:value>${queueName}</shell:value>
+                </shell:property>
+            </shell:configuration>
+            <shell:exec>echo</shell:exec>
+            <shell:argument>my_output=Hello Oozie</shell:argument>
+            <shell:capture-output/>
+        </shell:shell>
+        <workflow:ok to="decision1"/>
+        <workflow:error to="email-on-error"/>
+    </workflow:action>
+.
+    <workflow:decision name="decision1">
+        <workflow:switch>
+            <workflow:case 
to="happy-path">${wf:actionData('parent')['my_output'] eq 'Hello 
Oozie'}</workflow:case>
+            <workflow:default to="sad-path"/>
+        </workflow:switch>
+    </workflow:decision>
+.
+    <workflow:action name="happy-path">
+        <shell:shell>
+            <shell:resource-manager>${resourceManager}</shell:resource-manager>
+            <shell:name-node>${nameNode}</shell:name-node>
+            <shell:configuration>
+                <shell:property>
+                    <shell:name>mapred.job.queue.name</shell:name>
+                    <shell:value>${queueName}</shell:value>
+                </shell:property>
+            </shell:configuration>
+            <shell:exec>echo</shell:exec>
+            <shell:argument>Happy path</shell:argument>
+        </shell:shell>
+        <workflow:ok to="end"/>
+        <workflow:error to="email-on-error"/>
+    </workflow:action>
+.
+    <workflow:action name="sad-path">
+        <shell:shell>
+            <shell:resource-manager>${resourceManager}</shell:resource-manager>
+            <shell:name-node>${nameNode}</shell:name-node>
+            <shell:configuration>
+                <shell:property>
+                    <shell:name>mapred.job.queue.name</shell:name>
+                    <shell:value>${queueName}</shell:value>
+                </shell:property>
+            </shell:configuration>
+            <shell:exec>echo</shell:exec>
+            <shell:argument>my_output=Hello Oozie</shell:argument>
+            <shell:argument>Sad path</shell:argument>
+            <shell:capture-output/>
+        </shell:shell>
+        <workflow:ok to="end"/>
+        <workflow:error to="email-on-error"/>
+    </workflow:action>
+.
+    <workflow:end name="end"/>
+.
+</workflow:workflow-app>
+</verbatim>
+
+---+++ Runtime Limitations
+
+Even if Fluent Job API tries to abstract away the task of assembly job 
descriptor XML files, there are some runtime
+limitations apart from the [[DG_FluentJobAPI#Non-goals][non-goals section]]. 
All such limitations are based on the current
+implementations and subject to further improvements and fixes.
+
+There is only one =kill= possibility in every =workflow=. That is, there can 
be defined only one =action= to be executed just before
+any other =action= turns to be =kill=ed. Furthermore, =kill= goes to =end= 
directly. That means, there cannot be defined an
+intricate network of =kill= nodes, cascading sometimes to other =action= 
nodes, avoiding going to =end= in the first place.
+
+There are places where =decision= node generation fails, throwing an 
=Exception=. The problem is that during the transformation,
+Fluent Job API reaches a state where there is a =fork= that transitions to two 
=decision= nodes, which in turn split into two paths
+each. One of the paths from the first =decision= joins a path from the other 
=decision=, but the remaining conditional paths never
+meet. Therefore, not all paths originating from the =fork= converge to the 
same =join=.
+
+---++ Appendixes
+
+---+++ AE.A Appendix A, API JAR format
+
+It's kept simple - all the necessary Java class files that are needed are 
packed into a JAR file, that has a =META-INF/MANIFEST.MF=
+with a single entry having the =Main-Class= attribute set to the fully 
qualified name of the entry class, the one that
+=implements WorkflowFactory=:
+<verbatim>
+Main-Class: org.apache.oozie.jobs.api.factory.MyFirstWorkflowFactory
+</verbatim>
+
+*An example of the command line assembly of such an API JAR:*
+<verbatim>
+jar cfe simple-workflow.jar 
org.apache.oozie.fluentjob.api.factory.MyFirstWorkflowFactory \
+-C /Users/forsage/Workspace/oozie/fluent-job/fluent-job-api/target/classes \
+org/apache/oozie/jobs/api/factory/MyFirstWorkflowFactory.class
+</verbatim>
+
+---+++ AE.B Appendix B, Some Useful Builder classes
+
+For a complete list of =Builder= classes, please have a look at 
=oozie-fluent-job-api= artifact's following packages:
+   * =org.apache.oozie.fluentjob.api.action= - =ActionBuilder= classes
+   * =org.apache.oozie.fluentjob.api.factory= - the single entry point, 
=WorkflowFactory= is here
+   * =org.apache.oozie.fluentjob.api.workflow= - workflow related =Builder= 
classes
+
+On examples how to use these please see =oozie-examples= artifact's 
=org.apache.oozie.example.fluentjob= package.
+
+---+++ AE.C Appendix C, How To Extend
+
+Sometimes there are new XSD versions of an existing custom or core workflow 
action, sometimes it's a new custom workflow action that
+gets introduced. In any case, Fluent Job API needs to keep up with the changes.
+
+Here are the steps needed:
+   * in =fluent-job-api/pom.xml= extend or modify =jaxb2-maven-plugin= section 
=sources= by a new =source=
+   * in =fluent-job-api/src/main/xjb/bindings.xml= extend by a new or modify 
an existing =jaxb:bindings=
+   * in =fluent-job-api=, =org.apache.oozie.fluentjob.api.mapping= package, 
introduce a new or modify an existing =DozerConverter=
+   * in =dozer_config.xml=, introduce a new or modify an existing =converter= 
inside =custom-converters=
+   * in =fluent-job-api=, =org.apache.oozie.fluentjob.api.action=, introduce a 
new =Action= and a new =Builder=
+   * write new / modify existing relevant unit and integration tests
+
+---+++ AE.D Appendix D, API compatibility guarantees
+
+Fluent Job API is available beginning version 5.1.0. It's marked 
[email protected]= (intended for use in Oozie itself) and
[email protected]= (no stability guarantees are provided across 
any level of release granularity) to indicate that for
+the next few minor releases it's bound to change a lot.
+
+Beginning from around 5.4.0 planning the next phase, 
[email protected]= (compatibility breaking only between minors),
+and a few minor releases later, [email protected]= (safe to use 
outside of Oozie).
+
+[[index][::Go back to Oozie Documentation Index::]]
+
+</noautolink>

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/pom.xml
----------------------------------------------------------------------
diff --git a/examples/pom.xml b/examples/pom.xml
index 420b4f8..680e3fb 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -87,6 +87,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.oozie</groupId>
+            <artifactId>oozie-fluent-job-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/CredentialsRetrying.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/CredentialsRetrying.java
 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/CredentialsRetrying.java
new file mode 100644
index 0000000..b450e75
--- /dev/null
+++ 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/CredentialsRetrying.java
@@ -0,0 +1,93 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import com.google.common.collect.Lists;
+import org.apache.oozie.fluentjob.api.action.*;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.*;
+
+/**
+ * This {@link WorkflowFactory} generates a workflow definition that has 
{@code credentials} section, and an action that has
+ * {@code retry-interval}, {@code retry-max}, and {@code retry-policy} 
attributes set.
+ * <p>
+ * Note that {@link WorkflowBuilder#withCredentials(Credentials)} doesn't 
necessarily have to be called, since if
+ * {@link WorkflowBuilder#credentialsBuilder} is emtpy by the time {@link 
WorkflowBuilder#build()} is called,
+ * {@link Workflow#credentials} is built based on all the {@link 
Node#getCredentials()} that have been added to that
+ * {@code Workflow} in beforehand.
+ * <p>
+ * Note also that when {@code WorkflowBuilder#withCredentials(Credentials)} is 
explicitly called, the {@code <workflowapp />}'s
+ * {@code <credential />} section is generated only by using the {@code 
Credentials} defined on the {@code Workflow} level.
+ * <p>
+ * This way, users can, if they want to, omit calling {@code 
WorkflowBuilder#withCredentials(Credentials)} by default, but can
+ * also override the autogenerated {@code <credentials />} section of {@code 
<workflowapp />} by explicitly calling that method
+ * after another call to {@link CredentialsBuilder#build()}.
+ * {@link CredentialsBuilder#build()}.
+ */
+public class CredentialsRetrying implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final Credential hbaseCredential = CredentialBuilder.create()
+                .withName("hbase")
+                .withType("hbase")
+                .build();
+
+        final Credential hcatalogCredential = CredentialBuilder.create()
+                .withName("hcatalog")
+                .withType("hcatalog")
+                .withConfigurationEntry("hcat.metastore.uri", 
"thrift://<host>:<port>")
+                .withConfigurationEntry("hcat.metastore.principal", 
"hive/<host>@<realm>")
+                .build();
+
+        final Credential hive2Credential = CredentialBuilder.create()
+                .withName("hive2")
+                .withType("hive2")
+                .withConfigurationEntry("jdbcUrl", "jdbc://<host>/<database>")
+                .build();
+
+        final ShellAction shellActionWithHBase = ShellActionBuilder
+                .create()
+                .withName("shell-with-hbase-credential")
+                .withCredential(hbaseCredential)
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withExecutable("call-hbase.sh")
+                .build();
+
+        Hive2ActionBuilder
+                .createFromExistingAction(shellActionWithHBase)
+                .withParent(shellActionWithHBase)
+                .withName("hive2-action-with-hcatalog-and-hive2-credentials")
+                .clearCredentials()
+                .withCredential(hcatalogCredential)
+                .withCredential(hive2Credential)
+                .withRetryInterval(1)
+                .withRetryMax(3)
+                .withRetryPolicy("exponential")
+                .withScript("call-hive2.sql")
+                .build();
+
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("workflow-with-credentials")
+                .withDagContainingNode(shellActionWithHBase)
+                .build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/Global.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/Global.java 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/Global.java
new file mode 100644
index 0000000..c50fda0
--- /dev/null
+++ b/examples/src/main/java/org/apache/oozie/example/fluentjob/Global.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.action.LauncherBuilder;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.GlobalBuilder;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+
+/**
+ * This {@link WorkflowFactory} generates a workflow definition that has 
global section.
+ */
+public class Global implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("workflow-with-global")
+                .withGlobal(GlobalBuilder.create()
+                        .withResourceManager("${resourceManager}")
+                        .withNameNode("${nameNode}")
+                        .withJobXml("job.xml")
+                        .withConfigProperty("key1", "value1")
+                        .withLauncher(new LauncherBuilder()
+                                .withMemoryMb(1024L)
+                                .withVCores(1L)
+                                .build())
+                        .build())
+                .build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/JavaMain.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/JavaMain.java 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/JavaMain.java
new file mode 100644
index 0000000..a8948fd
--- /dev/null
+++ b/examples/src/main/java/org/apache/oozie/example/fluentjob/JavaMain.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.action.EmailActionBuilder;
+import org.apache.oozie.fluentjob.api.action.ErrorHandler;
+import org.apache.oozie.fluentjob.api.action.JavaAction;
+import org.apache.oozie.fluentjob.api.action.JavaActionBuilder;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+
+/**
+ * This {@link WorkflowFactory} generates the exact same workflow definition 
as {@code apps/java-main/workflow.xml}.
+ */
+public class JavaMain implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final JavaAction parent = JavaActionBuilder.create()
+                .withName("java-main")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                .withMainClass("org.apache.oozie.example.DemoJavaMain")
+                .withArg("Hello")
+                .withArg("Oozie!")
+                
.withErrorHandler(ErrorHandler.buildAsErrorHandler(EmailActionBuilder.create()
+                        .withName("email-on-error")
+                        .withRecipient("[email protected]")
+                        .withSubject("Workflow error")
+                        .withBody("Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]")))
+                .build();
+
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("java-main-example")
+                .withDagContainingNode(parent).build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/MultipleShellActions.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/MultipleShellActions.java
 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/MultipleShellActions.java
new file mode 100644
index 0000000..e1185b1
--- /dev/null
+++ 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/MultipleShellActions.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.action.EmailActionBuilder;
+import org.apache.oozie.fluentjob.api.action.ErrorHandler;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+import org.apache.oozie.fluentjob.api.action.ShellAction;
+import org.apache.oozie.fluentjob.api.action.ShellActionBuilder;
+
+/**
+ * An easily understandable {@link WorkflowFactory} that creates a {@link 
Workflow} instance consisting of
+ * multiple {@link ShellAction}s, the latter depending conditionally on the 
output of the former.
+ * <p>
+ * It demonstrates how the Jobs API can be used to create dynamic {@code 
Workflow} artifacts, as well as
+ * serves as an input for {@code TestOozieCLI} methods that check, submit or 
run Jobs API {@code .jar} files.
+ */
+public class MultipleShellActions implements WorkflowFactory {
+
+    @Override
+    public Workflow create() {
+        final ShellAction parent = ShellActionBuilder.create()
+                .withName("parent")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                .withArgument("my_output=Hello Oozie")
+                .withExecutable("echo")
+                .withCaptureOutput(true)
+                
.withErrorHandler(ErrorHandler.buildAsErrorHandler(EmailActionBuilder.create()
+                        .withName("email-on-error")
+                        .withRecipient("[email protected]")
+                        .withSubject("Workflow error")
+                        .withBody("Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]")))
+                .build();
+
+        ShellAction next = parent;
+
+        for (int ixShellPair = 0; ixShellPair < 5; ixShellPair++) {
+            final ShellAction happyPath = 
ShellActionBuilder.createFromExistingAction(parent)
+                    .withName("happy-path-" + ixShellPair)
+                    .withParentWithCondition(next, "${wf:actionData('" + 
next.getName() + "')['my_output'] eq 'Hello Oozie'}")
+                    .build();
+
+            ShellActionBuilder.createFromExistingAction(parent)
+                    .withName("sad-path-" + ixShellPair)
+                    .withParentDefaultConditional(next)
+                    .withArgument("Sad path " + ixShellPair)
+                    .withCaptureOutput(null)
+                    .build();
+
+            next = happyPath;
+        }
+
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("shell-example")
+                .withDagContainingNode(parent).build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/Parameters.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/Parameters.java 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/Parameters.java
new file mode 100644
index 0000000..18311be
--- /dev/null
+++ b/examples/src/main/java/org/apache/oozie/example/fluentjob/Parameters.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+
+/**
+ * This {@link WorkflowFactory} generates a workflow definition that has 
parameters section.
+ */
+public class Parameters implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("workflow-with-parameters")
+                .withParameter("name1", "value1")
+                .withParameter("name2", "value2", "description2")
+                .build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/Shell.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/Shell.java 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/Shell.java
new file mode 100644
index 0000000..b20a92a
--- /dev/null
+++ b/examples/src/main/java/org/apache/oozie/example/fluentjob/Shell.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.action.*;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+
+/**
+ * This {@link WorkflowFactory} generates a similar workflow definition to 
{@code apps/shell/workflow.xml}.
+ */
+public class Shell implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final ShellAction parent = ShellActionBuilder.create()
+                .withName("parent")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                .withArgument("my_output=Hello Oozie")
+                .withExecutable("echo")
+                .withCaptureOutput(true)
+                
.withErrorHandler(ErrorHandler.buildAsErrorHandler(EmailActionBuilder.create()
+                        .withName("email-on-error")
+                        .withRecipient("[email protected]")
+                        .withSubject("Workflow error")
+                        .withBody("Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]")))
+                .build();
+
+        ShellActionBuilder.createFromExistingAction(parent)
+                .withName("happy-path")
+                .withParentWithCondition(parent, 
"${wf:actionData('parent')['my_output'] eq 'Hello Oozie'}")
+                .withoutArgument("my_output=Hello Oozie")
+                .withArgument("Happy path")
+                .withCaptureOutput(null)
+                .build();
+
+        ShellActionBuilder.createFromExistingAction(parent)
+                .withName("sad-path")
+                .withParentDefaultConditional(parent)
+                .withArgument("Sad path")
+                .build();
+
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("shell-example")
+                .withDagContainingNode(parent).build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/examples/src/main/java/org/apache/oozie/example/fluentjob/Spark.java
----------------------------------------------------------------------
diff --git 
a/examples/src/main/java/org/apache/oozie/example/fluentjob/Spark.java 
b/examples/src/main/java/org/apache/oozie/example/fluentjob/Spark.java
new file mode 100644
index 0000000..6065933
--- /dev/null
+++ b/examples/src/main/java/org/apache/oozie/example/fluentjob/Spark.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.example.fluentjob;
+
+import org.apache.oozie.fluentjob.api.action.*;
+import org.apache.oozie.fluentjob.api.factory.WorkflowFactory;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+import org.apache.oozie.fluentjob.api.workflow.WorkflowBuilder;
+
+/**
+ * This {@link WorkflowFactory} generates the exact same workflow definition 
as {@code apps/spark/workflow.xml}.
+ */
+public class Spark implements WorkflowFactory {
+    @Override
+    public Workflow create() {
+        final Prepare prepare = new PrepareBuilder()
+                
.withDelete("${nameNode}/user/${wf:user()}/${examplesRoot}/output-data/spark")
+                .build();
+
+        final SparkAction parent = SparkActionBuilder.create()
+                .withName("spark-file-copy")
+                .withResourceManager("${resourceManager}")
+                .withNameNode("${nameNode}")
+                .withPrepare(prepare)
+                .withConfigProperty("mapred.job.queue.name", "${queueName}")
+                
.withArg("${nameNode}/user/${wf:user()}/${examplesRoot}/input-data/text/data.txt")
+                
.withArg("${nameNode}/user/${wf:user()}/${examplesRoot}/output-data/spark")
+                .withMaster("${master}")
+                .withActionName("Spark File Copy Example")
+                .withActionClass("org.apache.oozie.example.SparkFileCopy")
+                
.withJar("${nameNode}/user/${wf:user()}/${examplesRoot}/apps/spark/lib/oozie-examples.jar")
+                
.withErrorHandler(ErrorHandler.buildAsErrorHandler(EmailActionBuilder.create()
+                        .withName("email-on-error")
+                        .withRecipient("[email protected]")
+                        .withSubject("Workflow error")
+                        .withBody("Shell action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]")))
+                .build();
+
+        final Workflow workflow = new WorkflowBuilder()
+                .withName("spark-file-copy")
+                .withDagContainingNode(parent).build();
+
+        return workflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/findbugs-filter.xml
----------------------------------------------------------------------
diff --git a/findbugs-filter.xml b/findbugs-filter.xml
index 03ee4d1..133178f 100644
--- a/findbugs-filter.xml
+++ b/findbugs-filter.xml
@@ -29,4 +29,16 @@
        <Method name="toString" />
        <Bug pattern="WMI_WRONG_MAP_ITERATOR" />
      </Match>
+
+    <!-- Don't have much control on generated JAXB2 classes -->
+    <Match>
+        <Package name="~org\.apache\.oozie\.fluentjob\.api\.generated.*" />
+        <Bug pattern="EQ_UNUSUAL" />
+    </Match>
+
+    <!-- Directory name is private static final -->
+    <Match>
+        <Class name="org.apache.oozie.fluentjob.api.GraphVisualization" />
+        <Bug pattern="WEAK_FILENAMEUTILS" />
+    </Match>
 </FindBugsFilter>

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/fluent-job/fluent-job-api/pom.xml
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/pom.xml 
b/fluent-job/fluent-job-api/pom.xml
new file mode 100644
index 0000000..4c9b853
--- /dev/null
+++ b/fluent-job/fluent-job-api/pom.xml
@@ -0,0 +1,212 @@
+<?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.oozie</groupId>
+        <artifactId>oozie-fluent-job</artifactId>
+        <version>5.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>oozie-fluent-job-api</artifactId>
+    <version>5.1.0-SNAPSHOT</version>
+    <description>Apache Oozie Fluent Job API</description>
+    <name>Apache Oozie Fluent Job API</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>guru.nidi</groupId>
+            <artifactId>graphviz-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.dozer</groupId>
+            <artifactId>dozer</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jvnet.jaxb2_commons</groupId>
+            <artifactId>jaxb2-basics</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-annotations</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludeSubProjects>false</excludeSubProjects>
+                    <excludes>
+                        <!-- excluding all as the root POM does the full 
check-->
+                        <exclude>**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        
<descriptor>../../src/main/assemblies/tools.xml</descriptor>
+                    </descriptors>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>xjc</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <arguments>
+                        <argument>-XhashCode</argument>
+                        <argument>-Xequals</argument>
+                        <argument>-Xnamespace-prefix</argument>
+                        <argument>-Xsetters</argument>
+                    </arguments>
+
+                    <sources>
+                        
<source>../../client/src/main/resources/distcp-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/email-action-0.2.xsd</source>
+                        
<source>../../client/src/main/resources/hive2-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/hive-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/oozie-sla-0.2.xsd</source>
+                        
<source>../../client/src/main/resources/oozie-workflow-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/shell-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/spark-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/sqoop-action-1.0.xsd</source>
+                        
<source>../../client/src/main/resources/ssh-action-0.2.xsd</source>
+                    </sources>
+                </configuration>
+
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jvnet.jaxb2_commons</groupId>
+                        <artifactId>jaxb2-basics</artifactId>
+                        <version>1.11.1</version>
+                    </dependency>
+                    <dependency>
+                        <groupId>org.jvnet.jaxb2_commons</groupId>
+                        <artifactId>jaxb2-namespace-prefix</artifactId>
+                        <version>1.3</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    
<excludePackageNames>org.apache.oozie.fluentjob.api.generated.*</excludePackageNames>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <excludes>**/generated/**/*</excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>generateDocs</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+                <property>
+                    <name>generateDocs</name>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <configuration>
+                            <linksource>true</linksource>
+                            <quiet>true</quiet>
+                            <verbose>false</verbose>
+                            <source>${maven.compile.source}</source>
+                            <charset>${maven.compile.encoding}</charset>
+                            <groups>
+                                <group>
+                                    <title>Fluent Job API</title>
+                                    <packages>
+                                        org.apache.oozie.fluentjob.api
+                                    </packages>
+                                </group>
+                            </groups>
+                            
<excludePackageNames>org.apache.oozie.fluentjob.api.generated.*</excludePackageNames>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>javadoc</goal>
+                                </goals>
+                                <phase>package</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/Condition.java
----------------------------------------------------------------------
diff --git 
a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/Condition.java
 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/Condition.java
new file mode 100644
index 0000000..dd18f7a
--- /dev/null
+++ 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/Condition.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A class representing a condition in the "switch statement" of an Oozie 
decision node.
+ */
+public class Condition {
+    private final String condition;
+    private final boolean isDefault;
+
+    private Condition(final String condition, final boolean isDefault) {
+        final boolean bothFieldsSet = condition == null && !isDefault;
+        final boolean bothFieldsUnset = condition != null && isDefault;
+        Preconditions.checkArgument(!bothFieldsSet && !bothFieldsUnset,
+                "Exactly one of 'condition' and 'isDefault' must be non-null 
or true (respectively).");
+
+        this.condition = condition;
+        this.isDefault = isDefault;
+    }
+
+    /**
+     * Creates an actual condition (as opposed to a default condition).
+     * @param condition The string defining the condition.
+     * @return A new actual condition.
+     */
+    public static Condition actualCondition(final String condition) {
+        Preconditions.checkArgument(condition != null, "The argument 
'condition' must not be null.");
+
+        return new Condition(condition, false);
+    }
+
+    /**
+     * Creates a new default condition. Every decision node must have a 
default path which is chosen if no other
+     * condition is true.
+     * @return A new default condition.
+     */
+    public static Condition defaultCondition() {
+        return new Condition(null, true);
+    }
+
+    /**
+     * Returns the string defining the condition or {@code null} if this is a 
default condition.
+     * @return The string defining the condition or {@code null} if this is a 
default condition.
+     */
+    public String getCondition() {
+        return condition;
+    }
+
+    /**
+     * Returns whether this condition is a default condition.
+     * @return {@code true} if this condition is a default condition, {@code 
false} otherwise.
+     */
+    public boolean isDefault() {
+        return isDefault;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final Condition other = (Condition) o;
+
+        if (isDefault != other.isDefault) {
+            return false;
+        }
+
+        return condition != null ? condition.equals(other.condition) : 
other.condition == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = condition != null ? condition.hashCode() : 0;
+        result = 31 * result + (isDefault ? 1 : 0);
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/GraphVisualization.java
----------------------------------------------------------------------
diff --git 
a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/GraphVisualization.java
 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/GraphVisualization.java
new file mode 100644
index 0000000..437d6b3
--- /dev/null
+++ 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/GraphVisualization.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api;
+
+import guru.nidi.graphviz.engine.Format;
+import guru.nidi.graphviz.engine.Graphviz;
+import guru.nidi.graphviz.model.MutableGraph;
+import guru.nidi.graphviz.parse.Parser;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.oozie.fluentjob.api.action.Node;
+import org.apache.oozie.fluentjob.api.dag.Decision;
+import org.apache.oozie.fluentjob.api.dag.Graph;
+import org.apache.oozie.fluentjob.api.dag.NodeBase;
+import org.apache.oozie.fluentjob.api.workflow.Workflow;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Given a {@link Graph} or a {@link Workflow} instance, creates a visually 
appealing output
+ * using {@code nidi-graphviz} library in either {@code .dot} or {@code .png} 
format into {@link #PARENT_FOLDER_NAME}.
+ * <p>
+ * Applying memory constraints, the width of the resulting {@code .png} is 
limited to {@link #PNG_WIDTH}.
+ */
+public class GraphVisualization {
+    private static final String PARENT_FOLDER_NAME = "target/graphviz";
+    private static final int PNG_WIDTH = 1024;
+
+    public static String graphToDot(final Graph graph) {
+        return nodeBasesToDot(graph.getNodes());
+    }
+
+    private static String nodeBasesToDot(final Collection<NodeBase> nodes) {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("digraph {\n");
+        for (final NodeBase node : nodes) {
+            final List<NodeBase> children = node.getChildren();
+
+            String style = "";
+            if (node instanceof Decision) {
+                style = "[style=dashed];";
+            }
+
+            for (final NodeBase child : children) {
+                final String s = String.format("\t\"%s\" -> \"%s\"%s%n", 
node.getName(), child.getName(), style);
+                builder.append(s);
+            }
+        }
+
+        builder.append("}");
+
+        return builder.toString();
+    }
+
+    private static String workflowToDot(final Workflow workflow) {
+        return nodesToDot(workflow.getNodes());
+    }
+
+    private static String nodesToDot(final Collection<Node> nodes) {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("digraph {\n");
+        for (final Node node : nodes) {
+            final List<Node> children = node.getAllChildren();
+
+            String style = "";
+            if (!node.getChildrenWithConditions().isEmpty()) {
+                style = "[style=dashed];";
+            }
+
+            for (final Node child : children) {
+                builder.append(String.format("\t\"%s\" -> \"%s\"%s%n", 
node.getName(), child.getName(), style));
+            }
+        }
+
+        builder.append("}");
+
+        return builder.toString();
+    }
+
+    public static void graphToPng(final Graph graph, final String fileName) 
throws IOException {
+        final MutableGraph mg = Parser.read(graphToDot(graph));
+        mg.setLabel(fileName);
+
+        Graphviz.fromGraph(mg)
+                .width(PNG_WIDTH)
+                .render(Format.PNG)
+                .toFile(new File(PARENT_FOLDER_NAME, 
FilenameUtils.getName(fileName)));
+    }
+
+    public static void workflowToPng(final Workflow workflow, final String 
fileName) throws IOException {
+        final MutableGraph mg = Parser.read(workflowToDot(workflow));
+        mg.setLabel(fileName);
+
+        Graphviz.fromGraph(mg)
+                .width(PNG_WIDTH)
+                .render(Format.PNG)
+                .toFile(new File(PARENT_FOLDER_NAME, 
FilenameUtils.getName(fileName)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/ModifyOnce.java
----------------------------------------------------------------------
diff --git 
a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/ModifyOnce.java
 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/ModifyOnce.java
new file mode 100644
index 0000000..9561372
--- /dev/null
+++ 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/ModifyOnce.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A generic wrapper class for a value that can be modified once after 
construction, but only once.
+ * @param <T> the generic type that can be modified once, but only once
+ */
+public class ModifyOnce<T> {
+    private T data;
+    private boolean modified;
+
+    /**
+     * Creates a new {@link ModifyOnce} object initialized to {@code null}.
+     */
+    public ModifyOnce() {
+        this(null);
+    }
+
+    /**
+     * Creates a new {@link ModifyOnce} object initialized to {@code 
defaultData}.
+     * @param defaultData The initial value of this {@link ModifyOnce} object.
+     */
+    public ModifyOnce(final T defaultData) {
+        this.data = defaultData;
+        this.modified = false;
+    }
+
+    /**
+     * Returns the wrapped value.
+     * @return The wrapped value.
+     */
+    public T get() {
+        return data;
+    }
+
+    /**
+     * Sets the wrapped value. If it is not the first modification attempt, 
{@link IllegalStateException} is thrown.
+     * @param data The new data to store.
+     *
+     * @throws IllegalStateException if this is not the first modification 
attempt.
+     */
+    public void set(final T data) {
+        Preconditions.checkState(!modified, "Has already been modified once.");
+
+        this.data = data;
+        this.modified = true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/8a0a6487/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributes.java
----------------------------------------------------------------------
diff --git 
a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributes.java
 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributes.java
new file mode 100644
index 0000000..692eafb
--- /dev/null
+++ 
b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributes.java
@@ -0,0 +1,258 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An immutable class holding data that is used by several actions. It should 
be constructed by using an
+ * {@link ActionAttributesBuilder}.
+ */
[email protected]
[email protected]
+public class ActionAttributes {
+    private final String resourceManager;
+    private final String nameNode;
+    private final Prepare prepare;
+    private final Streaming streaming;
+    private final Pipes pipes;
+    private final ImmutableList<String> jobXmls;
+    private final ImmutableMap<String, String> configuration;
+    private final String configClass;
+    private final ImmutableList<String> files;
+    private final ImmutableList<String> archives;
+    private final ImmutableList<Delete> deletes;
+    private final ImmutableList<Mkdir> mkdirs;
+    private final ImmutableList<Move> moves;
+    private final ImmutableList<Chmod> chmods;
+    private final ImmutableList<Touchz> touchzs;
+    private final ImmutableList<Chgrp> chgrps;
+    private final String javaOpts;
+    private final ImmutableList<String> args;
+    private final Launcher launcher;
+    private final Boolean captureOutput;
+
+    ActionAttributes(final String resourceManager,
+                     final String nameNode,
+                     final Prepare prepare,
+                     final Streaming streaming,
+                     final Pipes pipes,
+                     final ImmutableList<String> jobXmls,
+                     final ImmutableMap<String, String> configuration,
+                     final String configClass,
+                     final ImmutableList<String> files,
+                     final ImmutableList<String> archives,
+                     final ImmutableList<Delete> deletes,
+                     final ImmutableList<Mkdir> mkdirs,
+                     final ImmutableList<Move> moves,
+                     final ImmutableList<Chmod> chmods,
+                     final ImmutableList<Touchz> touchzs,
+                     final ImmutableList<Chgrp> chgrps,
+                     final String javaOpts,
+                     final ImmutableList<String> args,
+                     final Launcher launcher,
+                     final Boolean captureOutput) {
+        this.resourceManager = resourceManager;
+        this.nameNode = nameNode;
+        this.prepare = prepare;
+        this.streaming = streaming;
+        this.pipes = pipes;
+        this.jobXmls = jobXmls;
+        this.configuration = configuration;
+        this.configClass = configClass;
+        this.files = files;
+        this.archives = archives;
+        this.deletes = deletes;
+        this.mkdirs = mkdirs;
+        this.moves = moves;
+        this.chmods = chmods;
+        this.touchzs = touchzs;
+        this.chgrps = chgrps;
+        this.javaOpts = javaOpts;
+        this.args = args;
+        this.launcher = launcher;
+        this.captureOutput = captureOutput;
+    }
+
+    /**
+     * Returns the resource manager address
+     * @return the resource manager address
+     */
+    public String getResourceManager() {
+        return resourceManager;
+    }
+
+    /**
+     * Returns the name node stored in this {@link ActionAttributes} object.
+     * @return The name node stored in this {@link ActionAttributes} object.
+     */
+    public String getNameNode() {
+        return nameNode;
+    }
+
+    /**
+     * Returns the {@link Prepare} object stored in this {@link 
ActionAttributes} object.
+     * @return The {@link Prepare} object stored in this {@link 
ActionAttributes} object.
+     */
+    public Prepare getPrepare() {
+        return prepare;
+    }
+
+    /**
+     * Returns the {@link Streaming} object stored in this {@link 
ActionAttributes} object.
+     * @return The {@link Streaming} object stored in this {@link 
ActionAttributes} object.
+     */
+    public Streaming getStreaming() {
+        return streaming;
+    }
+
+    /**
+     * Returns the {@link Pipes} object stored in this {@link 
ActionAttributes} object.
+     * @return The {@link Pipes} object stored in this {@link 
ActionAttributes} object.
+     */
+    public Pipes getPipes() {
+        return pipes;
+    }
+
+    /**
+     * Returns the list of job XMLs stored in this {@link ActionAttributes} 
object.
+     * @return The list of job XMLs stored in this {@link ActionAttributes} 
object.
+     */
+    public List<String> getJobXmls() {
+        return jobXmls;
+    }
+
+    /**
+     * Returns a map of the configuration key-value pairs stored in this 
{@link ActionAttributes} object.
+     * @return A map of the configuration key-value pairs stored in this 
{@link ActionAttributes} object.
+     */
+    public Map<String, String> getConfiguration() {
+        return configuration;
+    }
+
+    /**
+     * Returns the configuration class property of this {@link 
ActionAttributes} object.
+     * @return The configuration class property of this {@link 
ActionAttributes} object.
+     */
+    public String getConfigClass() {
+        return configClass;
+    }
+
+    /**
+     * Returns a list of the names of the files associated with this {@link 
ActionAttributes} object.
+     * @return A list of the names of the files associated with this {@link 
ActionAttributes} object.
+     */
+    public List<String> getFiles() {
+        return files;
+    }
+
+    /**
+     * Returns a list of the names of the archives associated with this {@link 
ActionAttributes} object.
+     * @return A list of the names of the archives associated with this {@link 
ActionAttributes} object.
+     */
+    public List<String> getArchives() {
+        return archives;
+    }
+
+    /**
+     * Returns a list of the {@link Delete} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Delete} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Delete> getDeletes() {
+        return deletes;
+    }
+
+    /**
+     * Returns a list of the {@link Mkdir} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Mkdir} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Mkdir> getMkdirs() {
+        return mkdirs;
+    }
+
+    /**
+     * Returns a list of the {@link Move} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Move} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Move> getMoves() {
+        return moves;
+    }
+
+    /**
+     * Returns a list of the {@link Chmod} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Chmod} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Chmod> getChmods() {
+        return chmods;
+    }
+
+    /**
+     * Returns a list of the {@link Touchz} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Touchz} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Touchz> getTouchzs() {
+        return touchzs;
+    }
+
+    /**
+     * Returns a list of the {@link Chgrp} objects stored in this {@link 
ActionAttributes} object.
+     * @return A list of the {@link Delete} objects stored in this {@link 
ActionAttributes} object.
+     */
+    public List<Chgrp> getChgrps() {
+        return chgrps;
+    }
+
+    /**
+     * Get the java options.
+     * @return the java options
+     */
+    public String getJavaOpts() {
+        return javaOpts;
+    }
+
+    /**
+     * Get all the arguments.
+     * @return the argument list
+     */
+    public List<String> getArgs() {
+        return args;
+    }
+
+    /**
+     * Get the {@link Launcher}
+     * @return the {@link Launcher}
+     */
+    public Launcher getLauncher() {
+        return launcher;
+    }
+
+    /**
+     * Tells the caller whether to capture output or not.
+     * @return {@code true} when capturing output
+     */
+    public boolean isCaptureOutput() {
+        return captureOutput == null ? false : captureOutput;
+    }
+}

Reply via email to