You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@oozie.apache.org by an...@apache.org on 2018/06/19 09:47:04 UTC

[14/16] oozie git commit: OOZIE-2339 [fluent-job] Minimum Viable Fluent Job API (daniel.becker, andras.piros via rkanter, gezapeti, pbacsko)

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/ActionAttributesBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributesBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributesBuilder.java
new file mode 100644
index 0000000..d52b9c9
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ActionAttributesBuilder.java
@@ -0,0 +1,567 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.oozie.fluentjob.api.ModifyOnce;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder class for {@link ActionAttributes}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link ActionAttributesBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ActionAttributesBuilder implements Builder<ActionAttributes> {
+    private final ModifyOnce<String> resourceManager;
+    private final ModifyOnce<String> nameNode;
+    private final ModifyOnce<Prepare> prepare;
+    private final ModifyOnce<Streaming> streaming;
+    private final ModifyOnce<Pipes> pipes;
+    private final List<String> jobXmls;
+    private final Map<String, ModifyOnce<String>> configuration;
+    private final ModifyOnce<String> configClass;
+    private final List<String> files;
+    private final List<String> archives;
+    private final List<Delete> deletes;
+    private final List<Mkdir> mkdirs;
+    private final List<Move> moves;
+    private final List<Chmod> chmods;
+    private final List<Touchz> touchzs;
+    private final List<Chgrp> chgrps;
+    private final ModifyOnce<String> javaOpts;
+    private final List<String> args;
+    private final ModifyOnce<Launcher> launcher;
+    private final ModifyOnce<Boolean> captureOutput;
+
+    /**
+     * Creates and returns an empty builder.
+     * @return An empty builder.
+     */
+    public static ActionAttributesBuilder create() {
+        final ModifyOnce<String> resourceManager = new ModifyOnce<>();
+        final ModifyOnce<String> nameNode = new ModifyOnce<>();
+        final ModifyOnce<Prepare> prepare = new ModifyOnce<>();
+        final ModifyOnce<Streaming> streaming = new ModifyOnce<>();
+        final ModifyOnce<Pipes> pipes = new ModifyOnce<>();
+        final List<String> jobXmls = new ArrayList<>();
+        final Map<String, ModifyOnce<String>> configuration = new LinkedHashMap<>();
+        final ModifyOnce<String> configClass = new ModifyOnce<>();
+        final List<String> files = new ArrayList<>();
+        final List<String> archives = new ArrayList<>();
+        final List<Delete> deletes = new ArrayList<>();
+        final List<Mkdir> mkdirs = new ArrayList<>();
+        final List<Move> moves = new ArrayList<>();
+        final List<Chmod> chmods = new ArrayList<>();
+        final List<Touchz> touchzs = new ArrayList<>();
+        final List<Chgrp> chgrps = new ArrayList<>();
+        final ModifyOnce<String> javaOpts = new ModifyOnce<>();
+        final List<String> args = new ArrayList<>();
+        final ModifyOnce<Launcher> launcher = new ModifyOnce<>();
+        final ModifyOnce<Boolean> captureOutput = new ModifyOnce<>();
+
+        return new ActionAttributesBuilder(
+                resourceManager,
+                prepare,
+                streaming,
+                pipes,
+                jobXmls,
+                configuration,
+                configClass,
+                files,
+                archives,
+                deletes,
+                mkdirs,
+                moves,
+                chmods,
+                touchzs,
+                chgrps,
+                javaOpts,
+                args,
+                nameNode,
+                launcher,
+                captureOutput);
+    }
+
+    /**
+     * Create and return a new {@link ActionAttributesBuilder} that is based on an already built
+     * {@link ActionAttributes} object. The properties of the builder will initially be the same as those of the
+     * provided {@link ActionAttributes} object, but it is possible to modify them once.
+     * @param attributes The {@link ActionAttributes} object on which this {@link ActionAttributesBuilder} will be based.
+     * @return A new {@link ActionAttributesBuilder} that is based on a previously built
+     *         {@link ActionAttributes} object.
+     */
+    public static ActionAttributesBuilder createFromExisting(final ActionAttributes attributes) {
+        final ModifyOnce<String> resourceManager = new ModifyOnce<>(attributes.getResourceManager());
+        final ModifyOnce<String> nameNode = new ModifyOnce<>(attributes.getNameNode());
+        final ModifyOnce<Prepare> prepare = new ModifyOnce<>(attributes.getPrepare());
+        final ModifyOnce<Streaming> streaming = new ModifyOnce<>(attributes.getStreaming());
+        final ModifyOnce<Pipes> pipes = new ModifyOnce<>(attributes.getPipes());
+        final List<String> jobXmls = new ArrayList<>(attributes.getJobXmls());
+        final Map<String, ModifyOnce<String>> configuration = convertToModifyOnceMap(attributes.getConfiguration());
+        final ModifyOnce<String> configClass = new ModifyOnce<>(attributes.getConfigClass());
+        final List<String> files = new ArrayList<>(attributes.getFiles());
+        final List<String> archives = new ArrayList<>(attributes.getArchives());
+        final List<Delete> deletes = new ArrayList<>(attributes.getDeletes());
+        final List<Mkdir> mkdirs = new ArrayList<>(attributes.getMkdirs());
+        final List<Move> moves = new ArrayList<>(attributes.getMoves());
+        final List<Chmod> chmods = new ArrayList<>(attributes.getChmods());
+        final List<Touchz> touchzs = new ArrayList<>(attributes.getTouchzs());
+        final List<Chgrp> chgrps = new ArrayList<>(attributes.getChgrps());
+        final ModifyOnce<String> javaOpts = new ModifyOnce<>(attributes.getJavaOpts());
+        final List<String> args = new ArrayList<>(attributes.getArgs());
+        final ModifyOnce<Launcher> launcher = new ModifyOnce<>(attributes.getLauncher());
+        final ModifyOnce<Boolean> captureOutput = new ModifyOnce<>(attributes.isCaptureOutput());
+
+        return new ActionAttributesBuilder(
+                resourceManager,
+                prepare,
+                streaming,
+                pipes,
+                jobXmls,
+                configuration,
+                configClass,
+                files,
+                archives,
+                deletes,
+                mkdirs,
+                moves,
+                chmods,
+                touchzs,
+                chgrps,
+                javaOpts,
+                args,
+                nameNode,
+                launcher,
+                captureOutput);
+    }
+
+    public static ActionAttributesBuilder createFromAction(final Node action) {
+        if (HasAttributes.class.isAssignableFrom(action.getClass()) && action instanceof HasAttributes) {
+            return ActionAttributesBuilder.createFromExisting(((HasAttributes) action).getAttributes());
+        }
+
+        return ActionAttributesBuilder.create();
+    }
+
+    private ActionAttributesBuilder(final ModifyOnce<String> resourceManager,
+                                    final ModifyOnce<Prepare> prepare,
+                                    final ModifyOnce<Streaming> streaming,
+                                    final ModifyOnce<Pipes> pipes,
+                                    final List<String> jobXmls,
+                                    final Map<String, ModifyOnce<String>> configuration,
+                                    final ModifyOnce<String> configClass,
+                                    final List<String> files,
+                                    final List<String> archives,
+                                    final List<Delete> deletes,
+                                    final List<Mkdir> mkdirs,
+                                    final List<Move> moves,
+                                    final List<Chmod> chmods,
+                                    final List<Touchz> touchzs,
+                                    final List<Chgrp> chgrps,
+                                    final ModifyOnce<String> javaOpts,
+                                    final List<String> args,
+                                    final ModifyOnce<String> nameNode,
+                                    final ModifyOnce<Launcher> launcher,
+                                    final ModifyOnce<Boolean> captureOutput) {
+        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.resourceManager = resourceManager;
+        this.launcher = launcher;
+        this.captureOutput = captureOutput;
+    }
+
+    public void withResourceManager(final String resourceManager) {
+        this.resourceManager.set(resourceManager);
+    }
+
+    /**
+     * Registers a name node.
+     * @param nameNode The string representing the name node.
+     * @throws IllegalStateException if a name node has already been set on this builder.
+     */
+    public void withNameNode(final String nameNode) {
+        this.nameNode.set(nameNode);
+    }
+
+    /**
+     * Registers a {@link Prepare} object.
+     * @param prepare The {@link Prepare} object to register.
+     * @throws IllegalStateException if a {@link Prepare} object has already been set on this builder.
+     */
+    void withPrepare(final Prepare prepare) {
+        this.prepare.set(prepare);
+    }
+
+    /**
+     * Registers a {@link Streaming} object.
+     * @param streaming The {@link Streaming} object to register.
+     * @throws IllegalStateException if a {@link Streaming} object has already been set on this builder.
+     */
+    void withStreaming(final Streaming streaming) {
+        this.streaming.set(streaming);
+    }
+
+    /**
+     * Registers a {@link Pipes} object.
+     * @param pipes The {@link Pipes} object to register.
+     * @throws IllegalStateException if a {@link Pipes} object has already been set on this builder.
+     */
+    void withPipes(final Pipes pipes) {
+        this.pipes.set(pipes);
+    }
+
+    /**
+     * Registers a job XML with this builder.
+     * @param jobXml The job XML to register.
+     */
+    public void withJobXml(final String jobXml) {
+        this.jobXmls.add(jobXml);
+    }
+
+    /**
+     * Removes a job XML if it is registered with this builder, otherwise does nothing.
+     * @param jobXml The job XML to remove.
+     */
+    public void withoutJobXml(final String jobXml) {
+        jobXmls.remove(jobXml);
+    }
+
+    /**
+     * Removes all job XMLs that are registered with this builder.
+     */
+    public void clearJobXmls() {
+        jobXmls.clear();
+    }
+
+    /**
+     * Registers a configuration property (a key-value pair) with this builder. If the provided key has already been
+     * set on this builder, an exception is thrown. Setting a key to null means deleting it.
+     * @param key The name of the property to set.
+     * @param value The value of the property to set.
+     * @throws IllegalStateException if the provided key has already been set on this builder.
+     */
+    public void withConfigProperty(final String key, final String value) {
+        ModifyOnce<String> mappedValue = this.configuration.get(key);
+
+        if (mappedValue == null) {
+            mappedValue = new ModifyOnce<>(value);
+            this.configuration.put(key, mappedValue);
+        }
+
+        mappedValue.set(value);
+    }
+
+    /**
+     * Registers a configuration class with this builder.
+     * @param configClass The string representing the configuration class.
+     * @throws IllegalStateException if a configuration class has already been set on this builder.
+     */
+    void withConfigClass(final String configClass) {
+        this.configClass.set(configClass);
+    }
+
+    /**
+     * Registers a file with this builder.
+     * @param file The file to register.
+     */
+    void withFile(final String file) {
+        this.files.add(file);
+    }
+
+    /**
+     * Removes a file if it is registered with this builder, otherwise does nothing.
+     * @param file The file to remove.
+     */
+    void withoutFile(final String file) {
+        files.remove(file);
+    }
+
+    /**
+     * Removes all files that are registered with this builder.
+     */
+    void clearFiles() {
+        files.clear();
+    }
+
+    /**
+     * Registers an archive with this builder.
+     * @param archive The archive to register.
+     */
+    void withArchive(final String archive) {
+        this.archives.add(archive);
+    }
+
+    /**
+     * Removes an archive if it is registered with this builder, otherwise does nothing.
+     * @param archive The archive to remove.
+     */
+    void withoutArchive(final String archive) {
+        archives.remove(archive);
+    }
+
+    /**
+     * Removes all archives that are registered with this builder.
+     */
+    void clearArchives() {
+        archives.clear();
+    }
+
+    /**
+     * Registers a {@link Delete} object with this builder.
+     * @param delete The {@link Delete} object to register.
+     */
+    void withDelete(final Delete delete) {
+        this.deletes.add(delete);
+    }
+
+    /**
+     * Removes a {@link Delete} object if it is registered with this builder, otherwise does nothing.
+     * @param delete The {@link Delete} object to remove.
+     */
+    void withoutDelete(final Delete delete) {
+        deletes.remove(delete);
+    }
+
+    /**
+     * Removes all {@link Delete} objects that are registered with this builder.
+     */
+    void clearDeletes() {
+        deletes.clear();
+    }
+
+    /**
+     * Registers a {@link Mkdir} object with this builder.
+     * @param mkdir The {@link Mkdir} object to register.
+     */
+    void withMkdir(final Mkdir mkdir) {
+        this.mkdirs.add(mkdir);
+    }
+
+    /**
+     * Removes a {@link Mkdir} object if it is registered with this builder, otherwise does nothing.
+     * @param mkdir The {@link Mkdir} object to remove.
+     */
+    void withoutMkdir(final Mkdir mkdir) {
+        mkdirs.remove(mkdir);
+    }
+
+    /**
+     * Removes all {@link Mkdir} objects that are registered with this builder.
+     */
+    void clearMkdirs() {
+        mkdirs.clear();
+    }
+
+    /**
+     * Registers a {@link Move} object with this builder.
+     * @param move The {@link Move} object to register.
+     */
+    void withMove(final Move move) {
+        this.moves.add(move);
+    }
+
+    /**
+     * Removes a {@link Move} object if it is registered with this builder, otherwise does nothing.
+     * @param move The {@link Move} object to remove.
+     */
+    void withoutMove(final Move move) {
+        moves.remove(move);
+    }
+
+    /**
+     * Removes all {@link Move} objects that are registered with this builder.
+     */
+    void clearMoves() {
+        moves.clear();
+    }
+
+    /**
+     * Registers a {@link Chmod} object with this builder.
+     * @param chmod The {@link Chmod} object to register.
+     */
+    void withChmod(final Chmod chmod) {
+        this.chmods.add(chmod);
+    }
+
+    /**
+     * Removes a {@link Chmod} object if it is registered with this builder, otherwise does nothing.
+     * @param chmod The {@link Chmod} object to remove.
+     */
+    void withoutChmod(final Chmod chmod) {
+        chmods.remove(chmod);
+    }
+
+    /**
+     * Removes all {@link Chmod} objects that are registered with this builder.
+     */
+    void clearChmods() {
+        chmods.clear();
+    }
+
+    /**
+     * Registers a {@link Touchz} object with this builder.
+     * @param touchz The {@link Touchz} object to register.
+     */
+    void withTouchz(final Touchz touchz) {
+        this.touchzs.add(touchz);
+    }
+
+    /**
+     * Removes a {@link Touchz} object if it is registered with this builder, otherwise does nothing.
+     * @param touchz The {@link Touchz} object to remove.
+     */
+    void withoutTouchz(final Touchz touchz) {
+        touchzs.remove(touchz);
+    }
+
+    /**
+     * Removes all {@link Touchz} objects that are registered with this builder.
+     */
+    void clearTouchzs() {
+        touchzs.clear();
+    }
+
+    /**
+     * Registers a {@link Chgrp} object with this builder.
+     * @param chgrp The {@link Chgrp} object to register.
+     */
+    void withChgrp(final Chgrp chgrp) {
+        this.chgrps.add(chgrp);
+    }
+
+    /**
+     * Removes a {@link Chgrp} object if it is registered with this builder, otherwise does nothing.
+     * @param chgrp The {@link Chgrp} object to remove.
+     */
+    void withoutChgrp(final Chgrp chgrp) {
+        chgrps.remove(chgrp);
+    }
+
+    /**
+     * Removes all {@link Chgrp} objects that are registered with this builder.
+     */
+    void clearChgrps() {
+        chgrps.clear();
+    }
+
+    void withJavaOpts(final String javaOpts) {
+        this.javaOpts.set(javaOpts);
+    }
+
+    void withArg(final String arg) {
+        this.args.add(arg);
+    }
+
+    void withoutArg(final String arg) {
+        this.args.remove(arg);
+    }
+
+    void clearArgs() {
+        args.clear();
+    }
+
+    public void withLauncher(final Launcher launcher) {
+        this.launcher.set(launcher);
+    }
+
+    void withCaptureOutput(final Boolean captureOutput) {
+        this.captureOutput.set(captureOutput);
+    }
+
+    /**
+     * Creates a new {@link ActionAttributes} object with the properties stores in this builder.
+     * The new {@link ActionAttributes} object is independent of this builder and the builder can be used to build
+     * new instances.
+     * @return A new {@link ActionAttributes} object with the propertied stores in this builder.
+     */
+    public ActionAttributes build() {
+        return new ActionAttributes(
+                resourceManager.get(),
+                nameNode.get(),
+                prepare.get(),
+                streaming.get(),
+                pipes.get(),
+                ImmutableList.copyOf(jobXmls),
+                convertToConfigurationMap(configuration),
+                configClass.get(),
+                ImmutableList.copyOf(files),
+                ImmutableList.copyOf(archives),
+                ImmutableList.copyOf(deletes),
+                ImmutableList.copyOf(mkdirs),
+                ImmutableList.copyOf(moves),
+                ImmutableList.copyOf(chmods),
+                ImmutableList.copyOf(touchzs),
+                ImmutableList.copyOf(chgrps),
+                javaOpts.get(),
+                ImmutableList.copyOf(args),
+                launcher.get(),
+                captureOutput.get());
+    }
+
+    static Map<String, ModifyOnce<String>> convertToModifyOnceMap(final Map<String, String> configurationMap) {
+        final Map<String, ModifyOnce<String>> modifyOnceEntries = new LinkedHashMap<>();
+
+        for (final Map.Entry<String, String> keyAndValue : configurationMap.entrySet()) {
+            modifyOnceEntries.put(keyAndValue.getKey(), new ModifyOnce<>(keyAndValue.getValue()));
+        }
+
+        return modifyOnceEntries;
+    }
+
+    static ImmutableMap<String, String> convertToConfigurationMap(final Map<String, ModifyOnce<String>> map) {
+        final Map<String, String> mutableConfiguration = new LinkedHashMap<>();
+
+        for (final Map.Entry<String, ModifyOnce<String>> modifyOnceEntry : map.entrySet()) {
+            if (modifyOnceEntry.getValue().get() != null) {
+                mutableConfiguration.put(modifyOnceEntry.getKey(), modifyOnceEntry.getValue().get());
+            }
+        }
+
+        return ImmutableMap.copyOf(mutableConfiguration);
+    }
+}

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/Builder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Builder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Builder.java
new file mode 100644
index 0000000..c71a04b
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Builder.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+/**
+ * A common interface for builders.
+ * @param <T> The type of the object that is build using this builder.
+ */
+public interface Builder<T> {
+    /**
+     * Builds and returns an object.
+     * @return The built object.
+     */
+    T build();
+}

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/ChFSBase.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBase.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBase.java
new file mode 100644
index 0000000..2fc1ff6
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBase.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A base class for {@link Chgrp} and {@link Chmod}.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ChFSBase {
+    private final boolean recursive;
+    private final String path;
+    private final String dirFiles;
+
+    ChFSBase(final ConstructionData constructionData) {
+        this.recursive = constructionData.recursive;
+        this.path = constructionData.path;
+        this.dirFiles = constructionData.dirFiles;
+    }
+
+    /**
+     * Returns whether this file system operation is recursive.
+     * @return {@code true} if this file system operation is recursive; {@code false} otherwise.
+     */
+    public boolean isRecursive() {
+        return recursive;
+    }
+
+    /**
+     * Returns the path of the target of this file system operation.
+     * @return The path of the target of this file system operation.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns whether this file system operation should be applied to all files in the given directory.
+     * @return "true" if this file system operation should be applied to all files in the given directory;
+     *         "false" otherwise.
+     */
+    public String getDirFiles() {
+        return dirFiles;
+    }
+
+    /**
+     * Helper class that is used by the subclasses of this class and their builders.
+     */
+    public static class ConstructionData {
+        private final boolean recursive;
+        private final String path;
+        private final String dirFiles;
+
+        public ConstructionData(final boolean recursive,
+                                final String path,
+                                final String dirFiles) {
+            this.recursive = recursive;
+            this.path = path;
+            this.dirFiles = dirFiles;
+        }
+    }
+}

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/ChFSBaseBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBaseBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBaseBuilder.java
new file mode 100644
index 0000000..d1303ac
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChFSBaseBuilder.java
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.oozie.fluentjob.api.ModifyOnce;
+
+/**
+ * A base class for {@link ChgrpBuilder} and {@link ChmodBuilder}.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public abstract class ChFSBaseBuilder <B extends ChFSBaseBuilder<B>> {
+    private final ModifyOnce<Boolean> recursive;
+    private final ModifyOnce<String> path;
+    private final ModifyOnce<String> dirFiles;
+
+    public ChFSBaseBuilder() {
+        recursive = new ModifyOnce<>(false);
+        path = new ModifyOnce<>();
+        dirFiles = new ModifyOnce<>("true");
+    }
+
+    /**
+     * Sets this file system operation to be recursive.
+     * @return This builder.
+     */
+    public B setRecursive() {
+        this.recursive.set(true);
+        return ensureRuntimeSelfReference();
+    }
+
+    /**
+     * Sets this file system operation to be non-recursive.
+     * @return This builder.
+     */
+    public B setNonRecursive() {
+        this.recursive.set(false);
+        return ensureRuntimeSelfReference();
+    }
+
+    /**
+     * Sets the path of the target of this file system operation.
+     * @param path the path of the target
+     * @return This builder.
+     */
+    public B withPath(final String path) {
+        this.path.set(path);
+        return ensureRuntimeSelfReference();
+    }
+
+    /**
+     * Sets whether this file system operation should be applied to all files in the given directory.
+     * @param dirFiles {@code true} if the operation should be applied to all files in the given directory;
+     *                 {@code false} if it shouldn't.
+     * @return This builder.
+     */
+    public B setDirFiles(final boolean dirFiles) {
+        this.dirFiles.set(Boolean.toString(dirFiles));
+        return ensureRuntimeSelfReference();
+    }
+
+    final B ensureRuntimeSelfReference() {
+        final B concrete = getRuntimeSelfReference();
+        if (concrete != this) {
+            throw new IllegalStateException(
+                    "The builder type B doesn't extend ChFSBaseBuilder<B>.");
+        }
+
+        return concrete;
+    }
+
+    protected ChFSBase.ConstructionData getConstructionData() {
+        return new ChFSBase.ConstructionData(recursive.get(), path.get(), dirFiles.get());
+    }
+
+    protected abstract B getRuntimeSelfReference();
+}

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/Chgrp.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chgrp.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chgrp.java
new file mode 100644
index 0000000..1fa77eb
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chgrp.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A class representing the chgrp command of {@link FSAction}.
+ * Instances of this class should be built using the builder {@link ChgrpBuilder}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link ChgrpBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class Chgrp extends ChFSBase {
+    private String group;
+
+    Chgrp(final ChFSBase.ConstructionData constructionData,
+          final String group) {
+        super(constructionData);
+        this.group = group;
+    }
+
+
+    /**
+     * Returns the new group that will be set by the command.
+     * @return The new group that will be set by the command.
+     */
+    public String getGroup() {
+        return group;
+    }
+}

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/ChgrpBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChgrpBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChgrpBuilder.java
new file mode 100644
index 0000000..05cbe9b
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChgrpBuilder.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.oozie.fluentjob.api.ModifyOnce;
+
+/**
+ * A builder class for {@link Chgrp}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link ChgrpBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ChgrpBuilder extends ChFSBaseBuilder<ChgrpBuilder> implements Builder<Chgrp> {
+    private final ModifyOnce<String> group;
+
+    public ChgrpBuilder() {
+        this.group = new ModifyOnce<>();
+    }
+
+    /**
+     * Sets the new group that will be set by the operation.
+     * @param group The group that will be set by the operation.
+     * @return This builder.
+     */
+    public ChgrpBuilder withGroup(final String group) {
+        this.group.set(group);
+        return this;
+    }
+
+    /**
+     * Builds and returns a new {@link Chgrp} object with the properties set in this builder.
+     * The new {@link Chgrp} object is independent of this builder and the builder can be used to build new instances.
+     * @return The newly built {@link Chgrp} object.
+     */
+    @Override
+    public Chgrp build() {
+        return new Chgrp(getConstructionData(), group.get());
+    }
+
+    @Override
+    protected ChgrpBuilder getRuntimeSelfReference() {
+        return this;
+    }
+}

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/Chmod.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chmod.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chmod.java
new file mode 100644
index 0000000..b0e998b
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Chmod.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A class representing the chmod command of {@link FSAction}.
+ * Instances of this class should be built using the builder {@link ChmodBuilder}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link ChmodBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class Chmod extends ChFSBase {
+    private final String permissions;
+
+    Chmod(final ChFSBase.ConstructionData constructionData,
+          final String permissions) {
+        super(constructionData);
+        this.permissions = permissions;
+    }
+
+    /**
+     * Returns the new permissions that will be set by the command.
+     * @return The new permissions that will be set by the command.
+     */
+    public String getPermissions() {
+        return permissions;
+    }
+}

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/ChmodBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChmodBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChmodBuilder.java
new file mode 100644
index 0000000..a6b439a
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ChmodBuilder.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.oozie.fluentjob.api.ModifyOnce;
+
+/**
+ * A builder class for {@link Chmod}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link ChmodBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ChmodBuilder extends ChFSBaseBuilder<ChmodBuilder> implements Builder<Chmod> {
+    private final ModifyOnce<String> permissions;
+
+    public ChmodBuilder() {
+        super();
+        permissions = new ModifyOnce<>();
+    }
+
+    /**
+     * Sets the new permissions that will be set by the operation.
+     * @param permissions The new permissions that will be set by the operation.
+     * @return This builder.
+     */
+    public ChmodBuilder withPermissions(final String permissions) {
+        this.permissions.set(permissions);
+        return this;
+    }
+
+    /**
+     * Builds and returns a new {@link Chmod} object with the properties set in this builder.
+     * The new {@link Chmod} object is independent of this builder and the builder can be used to build new instances.
+     * @return The newly built {@link Chmod} object.
+     */
+    public Chmod build() {
+        return new Chmod(getConstructionData(), permissions.get());
+    }
+
+    @Override
+    protected ChmodBuilder getRuntimeSelfReference() {
+        return this;
+    }
+}

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/Delete.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Delete.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Delete.java
new file mode 100644
index 0000000..17aab7f
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/Delete.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A class representing the delete operation of {@link FSAction} and the prepare section of other actions.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class Delete {
+    private final String path;
+    private final Boolean skipTrash;
+
+    /**
+     * Creates a new {@link Delete} object.
+     * @param path The path of the file or directory to be deleted.
+     * @param skipTrash {@code true} if the deleted items should NOT be moved to the trash but deleted completely;
+     *                  {@code false} if the items should be moved to trash instead of deleting them conpletely.
+     */
+    public Delete(final String path, final Boolean skipTrash) {
+        this.path = path;
+        this.skipTrash = skipTrash;
+    }
+
+    /**
+     * Returns the path of the item to be deleted.
+     * @return The path of the item to be deleted.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns whether the trash should be skipped when deleting the items.
+     * @return {@code true} if the deleted items should NOT be moved to the trash but deleted completely;
+     *         {@code false} if the items should be moved to trash instead of deleting them conpletely.
+     */
+    public Boolean getSkipTrash() {
+        return skipTrash;
+    }
+}
\ 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/action/DistcpAction.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpAction.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpAction.java
new file mode 100644
index 0000000..3886af6
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpAction.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents the Oozie DistCp action.
+ * Instances of this class should be built using the builder {@link DistcpActionBuilder}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link DistcpActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class DistcpAction extends Node implements HasAttributes {
+    private final ActionAttributes attributes;
+
+    DistcpAction(final ConstructionData constructionData,
+                 final ActionAttributes attributes) {
+        super(constructionData);
+
+        this.attributes = attributes;
+    }
+
+    public String getResourceManager() {
+        return attributes.getResourceManager();
+    }
+
+    public String getNameNode() {
+        return attributes.getNameNode();
+    }
+
+    public Prepare getPrepare() {
+        return attributes.getPrepare();
+    }
+
+    public String getConfigProperty(final String property) {
+        return attributes.getConfiguration().get(property);
+    }
+
+    public Map<String, String> getConfiguration() {
+        return attributes.getConfiguration();
+    }
+
+    public String getJavaOpts() {
+        return attributes.getJavaOpts();
+    }
+
+    public List<String> getArgs() {
+        return attributes.getArgs();
+    }
+
+    public ActionAttributes getAttributes() {
+        return attributes;
+    }
+}
\ 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/action/DistcpActionBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpActionBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpActionBuilder.java
new file mode 100644
index 0000000..42731ef
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/DistcpActionBuilder.java
@@ -0,0 +1,118 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A builder class for {@link DistcpAction}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link DistcpActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class DistcpActionBuilder extends NodeBuilderBaseImpl<DistcpActionBuilder> implements Builder<DistcpAction> {
+    private final ActionAttributesBuilder attributesBuilder;
+
+    public static DistcpActionBuilder create() {
+        final ActionAttributesBuilder builder = ActionAttributesBuilder.create();
+
+        return new DistcpActionBuilder(
+                null,
+                builder);
+    }
+
+    public static DistcpActionBuilder createFromExistingAction(final Node action) {
+        final ActionAttributesBuilder builder = ActionAttributesBuilder.createFromAction(action);
+
+        return new DistcpActionBuilder(action,
+                builder);
+    }
+
+    DistcpActionBuilder(final Node action,
+                        final ActionAttributesBuilder attributesBuilder) {
+        super(action);
+
+        this.attributesBuilder = attributesBuilder;
+    }
+
+    public DistcpActionBuilder withResourceManager(final String resourceManager) {
+        attributesBuilder.withResourceManager(resourceManager);
+        return this;
+    }
+
+    public DistcpActionBuilder withNameNode(final String nameNode) {
+        attributesBuilder.withNameNode(nameNode);
+        return this;
+    }
+
+    public DistcpActionBuilder withPrepare(final Prepare prepare) {
+        attributesBuilder.withPrepare(prepare);
+        return this;
+    }
+
+    public DistcpActionBuilder withConfigProperty(final String key, final String value) {
+        attributesBuilder.withConfigProperty(key, value);
+        return this;
+    }
+
+    public DistcpActionBuilder withJavaOpts(final String javaOpts) {
+        attributesBuilder.withJavaOpts(javaOpts);
+        return this;
+    }
+
+    public DistcpActionBuilder withArg(final String arg) {
+        attributesBuilder.withArg(arg);
+        return this;
+    }
+
+    public DistcpActionBuilder withoutArg(final String arg) {
+        attributesBuilder.withoutArg(arg);
+        return this;
+    }
+
+    public DistcpActionBuilder clearArgs() {
+        attributesBuilder.clearArgs();
+        return this;
+    }
+
+    @Override
+    public DistcpAction build() {
+        final Node.ConstructionData constructionData = getConstructionData();
+
+        final DistcpAction instance = new DistcpAction(
+                constructionData,
+                attributesBuilder.build());
+
+        addAsChildToAllParents(instance);
+
+        return instance;
+    }
+
+    @Override
+    protected DistcpActionBuilder getRuntimeSelfReference() {
+        return this;
+    }
+}

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/EmailAction.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailAction.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailAction.java
new file mode 100644
index 0000000..bbaecbc
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailAction.java
@@ -0,0 +1,118 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A class representing the Oozie email action.
+ * Instances of this class should be built using the builder {@link EmailActionBuilder}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link EmailActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class EmailAction extends Node {
+    private final String to;
+    private final String cc;
+    private final String bcc;
+    private final String subject;
+    private final String body;
+    private final String contentType;
+    private final String attachment;
+
+    EmailAction(final Node.ConstructionData constructionData,
+                final String to,
+                final String cc,
+                final String bcc,
+                final String subject,
+                final String body,
+                final String contentType,
+                final String attachment) {
+        super(constructionData);
+        this.to = to;
+        this.cc = cc;
+        this.bcc = bcc;
+        this.subject = subject;
+        this.body = body;
+        this.contentType = contentType;
+        this.attachment = attachment;
+    }
+
+    /**
+     * Returns the address of the recipient of the email.
+     * @return The address of the recipient of the email.
+     */
+    public String getRecipient() {
+        return to;
+    }
+
+    /**
+     * Returns the address of the recipient of a copy of the email.
+     * @return The address of the recipient of a copy of the email.
+     */
+    public String getCc() {
+        return cc;
+    }
+
+    /**
+     * Returns the address of the secret recipient of a copy of the email.
+     * @return The address of the secret recipient of a copy of the email.
+     */
+    public String getBcc() {
+        return bcc;
+    }
+
+    /**
+     * Returns the subject of the email.
+     * @return The subject of the email.
+     */
+    public String getSubject() {
+        return subject;
+    }
+
+    /**
+     * Returns the body of the email.
+     * @return The body of the email.
+     */
+    public String getBody() {
+        return body;
+    }
+
+    /**
+     * Returns the content type of the email.
+     * @return The content type of the email.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+    /**
+     * Returns the attachment of the email.
+     * @return The attachment of the email.
+     */
+    public String getAttachment() {
+        return attachment;
+    }
+}

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/EmailActionBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailActionBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailActionBuilder.java
new file mode 100644
index 0000000..fb78f97
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/EmailActionBuilder.java
@@ -0,0 +1,234 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.oozie.fluentjob.api.ModifyOnce;
+
+/**
+ * A builder class for {@link EmailAction}.
+ * <p>
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ * <p>
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link EmailActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class EmailActionBuilder extends NodeBuilderBaseImpl<EmailActionBuilder> implements Builder<EmailAction> {
+    private final ModifyOnce<String> to;
+    private final ModifyOnce<String> cc;
+    private final ModifyOnce<String> bcc;
+    private final ModifyOnce<String> subject;
+    private final ModifyOnce<String> body;
+    private final ModifyOnce<String> contentType;
+    private final ModifyOnce<String> attachment;
+
+    /**
+     * Creates and returns an empty builder.
+     * @return An empty builder.
+     */
+    public static EmailActionBuilder create() {
+        final ModifyOnce<String> to = new ModifyOnce<>();
+        final ModifyOnce<String> cc = new ModifyOnce<>();
+        final ModifyOnce<String> bcc = new ModifyOnce<>();
+        final ModifyOnce<String> subject = new ModifyOnce<>();
+        final ModifyOnce<String> body = new ModifyOnce<>();
+        final ModifyOnce<String> contentType = new ModifyOnce<>();
+        final ModifyOnce<String> attachment = new ModifyOnce<>();
+
+        return new EmailActionBuilder(
+                null,
+                to,
+                cc,
+                bcc,
+                subject,
+                body,
+                contentType,
+                attachment);
+    }
+
+    /**
+     * Create and return a new {@link EmailActionBuilder} that is based on an already built
+     * {@link EmailAction} object. The properties of the builder will initially be the same as those of the
+     * provided {@link EmailAction} object, but it is possible to modify them once.
+     * @param action The {@link EmailAction} object on which this {@link EmailActionBuilder} will be based.
+     * @return A new {@link EmailActionBuilder} that is based on a previously built {@link EmailAction} object.
+     */
+    public static EmailActionBuilder createFromExistingAction(final EmailAction action) {
+        final ModifyOnce<String> to = new ModifyOnce<>(action.getRecipient());
+        final ModifyOnce<String> cc = new ModifyOnce<>(action.getCc());
+        final ModifyOnce<String> bcc = new ModifyOnce<>(action.getBcc());
+        final ModifyOnce<String> subject = new ModifyOnce<>(action.getSubject());
+        final ModifyOnce<String> body = new ModifyOnce<>(action.getBody());
+        final ModifyOnce<String> contentType = new ModifyOnce<>(action.getContentType());
+        final ModifyOnce<String> attachment = new ModifyOnce<>(action.getAttachment());
+
+        return new EmailActionBuilder(
+                action,
+                to,
+                cc,
+                bcc,
+                subject,
+                body,
+                contentType,
+                attachment);
+    }
+
+    public static EmailActionBuilder createFromExistingAction(final Node action) {
+        final ModifyOnce<String> to = new ModifyOnce<>();
+        final ModifyOnce<String> cc = new ModifyOnce<>();
+        final ModifyOnce<String> bcc = new ModifyOnce<>();
+        final ModifyOnce<String> subject = new ModifyOnce<>();
+        final ModifyOnce<String> body = new ModifyOnce<>();
+        final ModifyOnce<String> contentType = new ModifyOnce<>();
+        final ModifyOnce<String> attachment = new ModifyOnce<>();
+
+        return new EmailActionBuilder(
+                action,
+                to,
+                cc,
+                bcc,
+                subject,
+                body,
+                contentType,
+                attachment);
+    }
+
+    EmailActionBuilder(final Node action,
+                       final ModifyOnce<String> to,
+                       final ModifyOnce<String> cc,
+                       final ModifyOnce<String> bcc,
+                       final ModifyOnce<String> subject,
+                       final ModifyOnce<String> body,
+                       final ModifyOnce<String> contentType,
+                       final ModifyOnce<String> attachment) {
+        super(action);
+        this.to = to;
+        this.cc = cc;
+        this.bcc = bcc;
+        this.subject = subject;
+        this.body = body;
+        this.contentType = contentType;
+        this.attachment = attachment;
+    }
+
+    /**
+     * Sets the address of the recipient of the email.
+     * @param to the recipient in To: field
+     * @return This builder.
+     */
+    public EmailActionBuilder withRecipient(final String to) {
+        this.to.set(to);
+        return this;
+    }
+
+    /**
+     * Sets the address of the recipient of a copy of the email.
+     * @param cc the recipient in CC: field
+     * @return This builder.
+     */
+    public EmailActionBuilder withCc(final String cc) {
+        this.cc.set(cc);
+        return this;
+    }
+
+    /**
+     * Sets the address of the secret recipient of a copy of the email.
+     * @param bcc the recipient in BCC: field
+     * @return This builder.
+     */
+    public EmailActionBuilder withBcc(final String bcc) {
+        this.bcc.set(bcc);
+        return this;
+    }
+
+    /**
+     * Sets the subject of the email.
+     * @param subject the email subject
+     * @return This builder.
+     */
+    public EmailActionBuilder withSubject(final String subject) {
+        this.subject.set(subject);
+        return this;
+    }
+
+    /**
+     * Sets the body of the email.
+     * @param body the email body
+     * @return This builder.
+     */
+    public EmailActionBuilder withBody(final String body) {
+        this.body.set(body);
+        return this;
+    }
+
+    /**
+     * Sets the content type of the email.
+     * @param contentType the email content type
+     * @return This builder
+     */
+    public EmailActionBuilder withContentType(final String contentType) {
+        this.contentType.set(contentType);
+        return this;
+    }
+
+    /**
+     * Sets the attachment of the email.
+     * @param attachment the email attachment path
+     * @return This builder.
+     */
+    public EmailActionBuilder withAttachment(final String attachment) {
+        this.attachment.set(attachment);
+        return this;
+    }
+
+    /**
+     * Creates a new {@link EmailAction} object with the properties stores in this builder.
+     * The new {@link EmailAction} object is independent of this builder and the builder can be used to build
+     * new instances.
+     * @return A new {@link EmailAction} object with the properties stored in this builder.
+     */
+    @Override
+    public EmailAction build() {
+        final Node.ConstructionData constructionData = getConstructionData();
+
+        final EmailAction instance = new EmailAction(
+                constructionData,
+                to.get(),
+                cc.get(),
+                bcc.get(),
+                subject.get(),
+                body.get(),
+                contentType.get(),
+                attachment.get());
+
+        addAsChildToAllParents(instance);
+
+        return instance;
+    }
+
+    @Override
+    protected EmailActionBuilder getRuntimeSelfReference() {
+        return this;
+    }
+}

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/ErrorHandler.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ErrorHandler.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ErrorHandler.java
new file mode 100644
index 0000000..6129b9f
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/ErrorHandler.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A class encapsulating an action so that it can be used as an error handler in a workflow.
+ *
+ * In an Oozie workflow definition (XML), every action has an "ok-transition" that is taken if the action completed
+ * successfully and an "error-transition" that is taken if the action failed. In this API, the dependency relations
+ * specified will be translated into "ok-transitions".
+ *
+ * If you would like to provide some error handling in case of action failure, you should add an {@link ErrorHandler}
+ * to the {@link 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.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ErrorHandler {
+    private final Node handlerNode;
+
+    /**
+     * Creates a new {@link ErrorHandler}. The provided builder is used to build the underlying error handler action.
+     * The builder should be in a state where no parents are specified, otherwise an exception is thrown.
+     * @param builder The builder that is used to build the underlying error handler node.
+     * @return A new {@link ErrorHandler}.
+     *
+     * @throws IllegalStateException if the provided builder has parents registered.
+     */
+    public static ErrorHandler buildAsErrorHandler(final Builder<? extends Node> builder) {
+        final Node handlerNode = builder.build();
+        return new ErrorHandler(handlerNode);
+    }
+
+    private ErrorHandler(final Node handlerNode) {
+        final boolean hasParents = !handlerNode.getAllParents().isEmpty();
+        final boolean hasChildren = !handlerNode.getAllChildren().isEmpty();
+        Preconditions.checkState(!hasParents && !hasChildren, "Error handler nodes cannot have parents or children.");
+
+        this.handlerNode = handlerNode;
+    }
+
+    /**
+     * Returns the name of the error handler action.
+     * @return The name of the error handler action.
+     */
+    public String getName() {
+        return handlerNode.getName();
+    }
+
+    /**
+     * Returns the error handler action node.
+     * @return The error handler action node.
+     */
+    public Node getHandlerNode() {
+        return handlerNode;
+    }
+}

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/FSAction.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSAction.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSAction.java
new file mode 100644
index 0000000..5d10e80
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSAction.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class representing the Oozie file system action.
+ * Instances of this class should be built using the builder {@link FSActionBuilder}.
+ *
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}.
+ *
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link FSActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class FSAction extends Node implements HasAttributes {
+    private final ActionAttributes attributes;
+
+    FSAction(final Node.ConstructionData constructionData,
+             final ActionAttributes attributes) {
+        super(constructionData);
+
+        this.attributes = attributes;
+    }
+
+    /**
+     * Returns the name node used by this {@link FSAction}.
+     * @return The name node used by this {@link FSAction}.
+     */
+    public String getNameNode() {
+        return attributes.getNameNode();
+    }
+
+    /**
+     * Returns the list of job XMLs used by this {@link FSAction}.
+     * @return The list of job XMLs used by this {@link FSAction}.
+     */
+    public List<String> getJobXmls() {
+        return attributes.getJobXmls();
+    }
+
+    /**
+     * Returns the value associated with the provided configuration property name.
+     * @param property The name of the configuration property for which the value will be returned.
+     * @return The value associated with the provided configuration property name.
+     */
+    public String getConfigProperty(final String property) {
+        return attributes.getConfiguration().get(property);
+    }
+
+    /**
+     * Returns all configuration properties of this {@link FSAction} as a map.
+     * @return All configuration properties of this {@link FSAction} as a map.
+     */
+    public Map<String, String> getConfiguration() {
+        return attributes.getConfiguration();
+    }
+
+    /**
+     * Returns the {@link Delete} objects that specify which directories or files will be deleted when running this action.
+     * @return The {@link Delete} objects that specify which directories or files will be deleted when running this action.
+     */
+    public List<Delete> getDeletes() {
+        return attributes.getDeletes();
+    }
+
+    /**
+     * Returns the {@link Mkdir} objects that specify which directories will be created when running this action.
+     * @return The {@link Mkdir} objects that specify which directories will be created when running this action.
+     */
+    public List<Mkdir> getMkdirs() {
+        return attributes.getMkdirs();
+    }
+
+    /**
+     * Returns the {@link Move} objects that specify which directories or files will be moved and where when running this action.
+     * @return The {@link Move} objects that specify which directories or files will be moved and where when running this action.
+     */
+    public List<Move> getMoves() {
+        return attributes.getMoves();
+    }
+
+    /**
+     * Returns the {@link Chmod} objects that specify the directories or files the permission of which will be changed
+     * and to what when running this action.
+     * @return The {@link Chmod} objects that specify the directories or files the permission of which will be changed
+     *         and to what when running this action.
+     */
+    public List<Chmod> getChmods() {
+        return attributes.getChmods();
+    }
+
+    /**
+     * Returns the {@link Touchz} objects that specify which files will be touched when running this action.
+     * @return The {@link Touchz} objects that specify which files will be touched when running this action.
+     */
+    public List<Touchz> getTouchzs() {
+        return attributes.getTouchzs();
+    }
+
+    /**
+     * Returns the {@link Chgrp} objects that specify the directories or files the owner / group of which will be changed
+     * and to what when running this action.
+     * @return The {@link Chgrp} objects that specify the directories or files the owner / group of which will be changed
+     *         and to what when running this action.
+     */
+    public List<Chgrp> getChgrps() {
+        return attributes.getChgrps();
+    }
+
+    public ActionAttributes getAttributes() {
+        return attributes;
+    }
+}

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/FSActionBuilder.java
----------------------------------------------------------------------
diff --git a/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSActionBuilder.java b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSActionBuilder.java
new file mode 100644
index 0000000..e70b061
--- /dev/null
+++ b/fluent-job/fluent-job-api/src/main/java/org/apache/oozie/fluentjob/api/action/FSActionBuilder.java
@@ -0,0 +1,323 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.oozie.fluentjob.api.action;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * A builder class for {@link FSAction}.
+ * <p>
+ * The properties of the builder can only be set once, an attempt to set them a second time will trigger
+ * an {@link IllegalStateException}. The properties that are lists are an exception to this rule, of course multiple
+ * elements can be added / removed.
+ * <p>
+ * Builder instances can be used to build several elements, although properties already set cannot be changed after
+ * a call to {@link FSActionBuilder#build} either.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class FSActionBuilder extends NodeBuilderBaseImpl<FSActionBuilder> implements Builder<FSAction> {
+    private final ActionAttributesBuilder attributesBuilder;
+
+    /**
+     * Creates and returns an empty builder.
+     * @return An empty builder.
+     */
+    public static FSActionBuilder create() {
+        final ActionAttributesBuilder builder = ActionAttributesBuilder.create();
+
+        return new FSActionBuilder(
+                null,
+                builder);
+    }
+
+    /**
+     * Create and return a new {@link FSActionBuilder} that is based on an already built
+     * {@link FSAction} object. The properties of the builder will initially be the same as those of the
+     * provided {@link FSAction} object, but it is possible to modify them once.
+     * @param action The {@link FSAction} object on which this {@link FSActionBuilder} will be based.
+     * @return A new {@link FSActionBuilder} that is based on a previously built {@link FSAction} object.
+     */
+    public static FSActionBuilder createFromExistingAction(final Node action) {
+        final ActionAttributesBuilder builder = ActionAttributesBuilder.createFromAction(action);
+
+        return new FSActionBuilder(
+                action,
+                builder);
+    }
+
+    FSActionBuilder(final Node action,
+                    final ActionAttributesBuilder attributesBuilder) {
+        super(action);
+
+        this.attributesBuilder = attributesBuilder;
+    }
+
+    /**
+     * Registers a name node.
+     * @param nameNode The string representing the name node.
+     * @return this
+     * @throws IllegalStateException if a name node has already been set on this builder.
+     */
+    public FSActionBuilder withNameNode(final String nameNode) {
+        attributesBuilder.withNameNode(nameNode);
+        return this;
+    }
+
+    /**
+     * Registers a job XML with this builder.
+     * @param jobXml The job XML to register.
+     * @return this
+     */
+    public FSActionBuilder withJobXml(final String jobXml) {
+        attributesBuilder.withJobXml(jobXml);
+        return this;
+    }
+
+    /**
+     * Removes a job XML if it is registered with this builder, otherwise does nothing.
+     * @param jobXml The job XML to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutJobXml(final String jobXml) {
+        attributesBuilder.withoutJobXml(jobXml);
+        return this;
+    }
+
+    /**
+     * Removes all job XMLs that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearJobXmls() {
+        attributesBuilder.clearJobXmls();
+        return this;
+    }
+
+    /**
+     * Registers a configuration property (a key-value pair) with this builder. If the provided key has already been
+     * set on this builder, an exception is thrown. Setting a key to null means deleting it.
+     * @param key The name of the property to set.
+     * @param value The value of the property to set.
+     * @return this
+     * @throws IllegalStateException if the provided key has already been set on this builder.
+     */
+    public FSActionBuilder withConfigProperty(final String key, final String value) {
+        attributesBuilder.withConfigProperty(key, value);
+        return this;
+    }
+
+    /**
+     * Registers a {@link Delete} object with this builder.
+     * @param delete The {@link Delete} object to register.
+     * @return this
+     */
+    public FSActionBuilder withDelete(final Delete delete) {
+        attributesBuilder.withDelete(delete);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Delete} object if it is registered with this builder, otherwise does nothing.
+     * @param delete The {@link Delete} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutDelete(final Delete delete) {
+        attributesBuilder.withoutDelete(delete);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Delete} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearDeletes() {
+        attributesBuilder.clearDeletes();
+        return this;
+    }
+
+    /**
+     * Registers a {@link Mkdir} object with this builder.
+     * @param mkdir The {@link Mkdir} object to register.
+     * @return this
+     */
+    public FSActionBuilder withMkdir(final Mkdir mkdir) {
+        attributesBuilder.withMkdir(mkdir);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Mkdir} object if it is registered with this builder, otherwise does nothing.
+     * @param mkdir The {@link Mkdir} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutMkdir(final Mkdir mkdir) {
+        attributesBuilder.withoutMkdir(mkdir);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Mkdir} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearMkdirs() {
+        attributesBuilder.clearMkdirs();
+        return this;
+    }
+
+    /**
+     * Registers a {@link Move} object with this builder.
+     * @param move The {@link Move} object to register.
+     * @return this
+     */
+    public FSActionBuilder withMove(final Move move) {
+        attributesBuilder.withMove(move);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Move} object if it is registered with this builder, otherwise does nothing.
+     * @param move The {@link Move} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutMove(final Move move) {
+        attributesBuilder.withoutMove(move);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Move} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearMoves() {
+        attributesBuilder.clearMoves();
+        return this;
+    }
+
+    /**
+     * Registers a {@link Chmod} object with this builder.
+     * @param chmod The {@link Chmod} object to register.
+     * @return this
+     */
+    public FSActionBuilder withChmod(final Chmod chmod) {
+        attributesBuilder.withChmod(chmod);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Chmod} object if it is registered with this builder, otherwise does nothing.
+     * @param chmod The {@link Chmod} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutChmod(final Chmod chmod) {
+        attributesBuilder.withoutChmod(chmod);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Chmod} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearChmods() {
+        attributesBuilder.clearChmods();
+        return this;
+    }
+
+    /**
+     * Registers a {@link Touchz} object with this builder.
+     * @param touchz The {@link Touchz} object to register.
+     * @return this
+     */
+    public FSActionBuilder withTouchz(final Touchz touchz) {
+        attributesBuilder.withTouchz(touchz);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Touchz} object if it is registered with this builder, otherwise does nothing.
+     * @param touchz The {@link Touchz} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutTouchz(final Touchz touchz) {
+        attributesBuilder.withoutTouchz(touchz);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Touchz} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearTouchzs() {
+        attributesBuilder.clearTouchzs();
+        return this;
+    }
+
+    /**
+     * Registers a {@link Chgrp} object with this builder.
+     * @param chgrp The {@link Chgrp} object to register.
+     * @return this
+     */
+    public FSActionBuilder withChgrp(final Chgrp chgrp) {
+        attributesBuilder.withChgrp(chgrp);
+        return this;
+    }
+
+    /**
+     * Removes a {@link Chgrp} object if it is registered with this builder, otherwise does nothing.
+     * @param chgrp The {@link Chgrp} object to remove.
+     * @return this
+     */
+    public FSActionBuilder withoutChgrp(final Chgrp chgrp) {
+        attributesBuilder.withoutChgrp(chgrp);
+        return this;
+    }
+
+    /**
+     * Removes all {@link Chgrp} objects that are registered with this builder.
+     * @return this
+     */
+    public FSActionBuilder clearChgrps() {
+        attributesBuilder.clearChgrps();
+        return this;
+    }
+
+    /**
+     * Creates a new {@link FSAction} object with the properties stores in this builder.
+     * The new {@link FSAction} object is independent of this builder and the builder can be used to build
+     * new instances.
+     * @return A new {@link FSAction} object with the properties stored in this builder.
+     */
+    @Override
+    public FSAction build() {
+        final Node.ConstructionData constructionData = getConstructionData();
+
+        final FSAction instance = new FSAction(
+                constructionData,
+                attributesBuilder.build());
+
+        addAsChildToAllParents(instance);
+
+        return instance;
+    }
+
+    @Override
+    protected FSActionBuilder getRuntimeSelfReference() {
+        return this;
+    }
+}