You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by pv...@apache.org on 2018/02/28 16:25:10 UTC

[16/20] nifi git commit: NIFI-4839 - Added abbreviation in simple output for name, description, and comments - Refactored so that commands produce a result which can then be written or used - Added support for back-referencing results, initially prototyp

NIFI-4839 - Added abbreviation in simple output for name, description, and comments
- Refactored so that commands produce a result which can then be written or used
- Added support for back-referencing results, initially prototyped by Andrew Grande
- Fixed dynamic table layout when writing simple results
- Added a new command group called 'demo' with a new 'quick-import' command
- Fixes/improvements after previous refactoring
- Created a reusable TableWriter and updating a few result classes to use it


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/b68eebd4
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/b68eebd4
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/b68eebd4

Branch: refs/heads/master
Commit: b68eebd4293bc921148d933ad94d890c388ce4d0
Parents: cc3c1b1
Author: Bryan Bende <bb...@apache.org>
Authored: Mon Feb 12 14:19:12 2018 -0500
Committer: Pierre Villard <pi...@gmail.com>
Committed: Wed Feb 28 17:23:44 2018 +0100

----------------------------------------------------------------------
 .../apache/nifi/toolkit/cli/CLICompleter.java   |  10 +-
 .../org/apache/nifi/toolkit/cli/CLIMain.java    |   5 -
 .../apache/nifi/toolkit/cli/api/Command.java    |  17 +-
 .../apache/nifi/toolkit/cli/api/Context.java    |   2 -
 .../nifi/toolkit/cli/api/ReferenceResolver.java |  37 ++
 .../nifi/toolkit/cli/api/Referenceable.java     |  29 ++
 .../org/apache/nifi/toolkit/cli/api/Result.java |  31 ++
 .../nifi/toolkit/cli/api/ResultWriter.java      |  65 ----
 .../nifi/toolkit/cli/api/WritableResult.java    |  36 ++
 .../impl/client/NiFiRegistryClientFactory.java  |   1 -
 .../cli/impl/command/AbstractCommand.java       |  61 ++--
 .../cli/impl/command/AbstractCommandGroup.java  |   5 +
 .../impl/command/AbstractPropertyCommand.java   |  24 +-
 .../cli/impl/command/CommandFactory.java        |   2 +
 .../toolkit/cli/impl/command/CommandOption.java |   3 +
 .../cli/impl/command/CommandProcessor.java      | 118 +++++--
 .../composite/AbstractCompositeCommand.java     | 137 +++++++
 .../command/composite/DemoCommandGroup.java     |  42 +++
 .../cli/impl/command/composite/QuickImport.java | 248 +++++++++++++
 .../toolkit/cli/impl/command/misc/Exit.java     |  12 +-
 .../toolkit/cli/impl/command/misc/Help.java     |  12 +-
 .../impl/command/nifi/AbstractNiFiCommand.java  |  20 +-
 .../cli/impl/command/nifi/flow/CurrentUser.java |  13 +-
 .../cli/impl/command/nifi/flow/GetRootId.java   |   9 +-
 .../impl/command/nifi/pg/PGChangeVersion.java   |   8 +-
 .../impl/command/nifi/pg/PGGetAllVersions.java  |  15 +-
 .../cli/impl/command/nifi/pg/PGGetVars.java     |  13 +-
 .../cli/impl/command/nifi/pg/PGGetVersion.java  |  12 +-
 .../cli/impl/command/nifi/pg/PGImport.java      |   9 +-
 .../cli/impl/command/nifi/pg/PGList.java        |  15 +-
 .../cli/impl/command/nifi/pg/PGSetVar.java      |   8 +-
 .../cli/impl/command/nifi/pg/PGStart.java       |   8 +-
 .../cli/impl/command/nifi/pg/PGStop.java        |  11 +-
 .../nifi/registry/CreateRegistryClient.java     |   9 +-
 .../nifi/registry/GetRegistryClientId.java      |  17 +-
 .../nifi/registry/ListRegistryClients.java      |  13 +-
 .../nifi/registry/UpdateRegistryClient.java     |   8 +-
 .../registry/AbstractNiFiRegistryCommand.java   |  20 +-
 .../command/registry/bucket/CreateBucket.java   |  10 +-
 .../command/registry/bucket/DeleteBucket.java   |   8 +-
 .../command/registry/bucket/ListBuckets.java    |  15 +-
 .../impl/command/registry/flow/CreateFlow.java  |  10 +-
 .../impl/command/registry/flow/DeleteFlow.java  |   8 +-
 .../registry/flow/ExportFlowVersion.java        |  21 +-
 .../registry/flow/ImportFlowVersion.java        |  15 +-
 .../command/registry/flow/ListFlowVersions.java |  13 +-
 .../impl/command/registry/flow/ListFlows.java   |  12 +-
 .../impl/command/registry/user/CurrentUser.java |  15 +-
 .../cli/impl/command/session/ClearSession.java  |   8 +-
 .../cli/impl/command/session/GetVariable.java   |  11 +-
 .../impl/command/session/RemoveVariable.java    |   8 +-
 .../cli/impl/command/session/SetVariable.java   |   8 +-
 .../cli/impl/command/session/ShowKeys.java      |  13 +-
 .../cli/impl/command/session/ShowSession.java   |   8 +-
 .../cli/impl/context/StandardContext.java       |  34 --
 .../cli/impl/result/AbstractWritableResult.java |  57 +++
 .../toolkit/cli/impl/result/BucketsResult.java  | 106 ++++++
 .../impl/result/CurrentUserEntityResult.java    |  48 +++
 .../cli/impl/result/CurrentUserResult.java      |  47 +++
 .../cli/impl/result/JsonResultWriter.java       | 111 ------
 .../cli/impl/result/ProcessGroupsResult.java    |  57 +++
 .../cli/impl/result/RegistryClientIDResult.java |  44 +++
 .../cli/impl/result/RegistryClientsResult.java  |  79 +++++
 .../cli/impl/result/SimpleResultWriter.java     | 354 -------------------
 .../toolkit/cli/impl/result/StringResult.java   |  45 +++
 .../cli/impl/result/VariableRegistryResult.java |  60 ++++
 .../impl/result/VersionControlInfoResult.java   |  55 +++
 .../VersionedFlowSnapshotMetadataResult.java    |  79 +++++
 .../VersionedFlowSnapshotMetadataSetResult.java |  64 ++++
 .../result/VersionedFlowSnapshotResult.java     |  65 ++++
 .../cli/impl/result/VersionedFlowsResult.java   | 107 ++++++
 .../nifi/toolkit/cli/impl/result/Void.java      |  20 ++
 .../toolkit/cli/impl/result/VoidResult.java     |  39 ++
 .../impl/result/writer/DynamicTableWriter.java  | 123 +++++++
 .../toolkit/cli/impl/result/writer/Table.java   |  92 +++++
 .../cli/impl/result/writer/TableColumn.java     |  59 ++++
 .../cli/impl/result/writer/TableWriter.java     |  34 ++
 .../cli/impl/session/SessionVariable.java       |  58 +++
 .../cli/impl/session/SessionVariables.java      |  58 ---
 .../nifi/toolkit/cli/NiFiCLIMainRunner.java     |   7 +-
 .../nifi/toolkit/cli/TestCLICompleter.java      |  11 +-
 .../cli/impl/result/TestBucketsResult.java      |  76 ++++
 .../impl/result/TestRegistryClientResult.java   |  89 +++++
 ...TestVersionedFlowSnapshotMetadataResult.java |  80 +++++
 .../impl/result/TestVersionedFlowsResult.java   |  76 ++++
 .../result/writer/TestDynamicTableWriter.java   | 123 +++++++
 86 files changed, 2734 insertions(+), 911 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java
index dad5416..7889b30 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java
@@ -20,7 +20,7 @@ import org.apache.nifi.toolkit.cli.api.Command;
 import org.apache.nifi.toolkit.cli.api.CommandGroup;
 import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
 import org.apache.nifi.toolkit.cli.impl.command.session.SessionCommandGroup;
-import org.apache.nifi.toolkit.cli.impl.session.SessionVariables;
+import org.apache.nifi.toolkit.cli.impl.session.SessionVariable;
 import org.jline.builtins.Completers;
 import org.jline.reader.Candidate;
 import org.jline.reader.Completer;
@@ -50,14 +50,16 @@ public class CLICompleter implements Completer {
         args.add("-" + CommandOption.PROPERTIES.getShortName());
         args.add("-" + CommandOption.INPUT_SOURCE.getShortName());
         args.add("-" + CommandOption.OUTPUT_FILE.getShortName());
+        args.add("-" + CommandOption.NIFI_REG_PROPS.getShortName());
+        args.add("-" + CommandOption.NIFI_PROPS.getShortName());
         FILE_COMPLETION_ARGS = Collections.unmodifiableSet(args);
     }
 
     private static final Set<String> FILE_COMPLETION_VARS;
     static {
         final Set<String> vars = new HashSet<>();
-        vars.add(SessionVariables.NIFI_CLIENT_PROPS.getVariableName());
-        vars.add(SessionVariables.NIFI_REGISTRY_CLIENT_PROPS.getVariableName());
+        vars.add(SessionVariable.NIFI_CLIENT_PROPS.getVariableName());
+        vars.add(SessionVariable.NIFI_REGISTRY_CLIENT_PROPS.getVariableName());
         FILE_COMPLETION_VARS = Collections.unmodifiableSet(vars);
     }
 
@@ -178,7 +180,7 @@ public class CLICompleter implements Completer {
                 // if we have two args then we are completing the variable name
                 // if we have three args, and the third is one a variable that is a file path, then we need a file completer
                 if (line.wordIndex() == 2) {
-                    addCandidates(SessionVariables.getAllVariableNames(), candidates);
+                    addCandidates(SessionVariable.getAllVariableNames(), candidates);
                 } else if (line.wordIndex() == 3) {
                     final String currWord = line.word();
                     final String prevWord = line.words().get(line.wordIndex() - 1);

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLIMain.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLIMain.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLIMain.java
index 95da2fc..643fe03 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLIMain.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLIMain.java
@@ -23,7 +23,6 @@ import org.apache.nifi.toolkit.cli.api.ClientFactory;
 import org.apache.nifi.toolkit.cli.api.Command;
 import org.apache.nifi.toolkit.cli.api.CommandGroup;
 import org.apache.nifi.toolkit.cli.api.Context;
-import org.apache.nifi.toolkit.cli.api.ResultType;
 import org.apache.nifi.toolkit.cli.api.Session;
 import org.apache.nifi.toolkit.cli.impl.client.NiFiClientFactory;
 import org.apache.nifi.toolkit.cli.impl.client.NiFiRegistryClientFactory;
@@ -31,8 +30,6 @@ import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
 import org.apache.nifi.toolkit.cli.impl.command.CommandFactory;
 import org.apache.nifi.toolkit.cli.impl.command.CommandProcessor;
 import org.apache.nifi.toolkit.cli.impl.context.StandardContext;
-import org.apache.nifi.toolkit.cli.impl.result.JsonResultWriter;
-import org.apache.nifi.toolkit.cli.impl.result.SimpleResultWriter;
 import org.apache.nifi.toolkit.cli.impl.session.InMemorySession;
 import org.apache.nifi.toolkit.cli.impl.session.PersistentSession;
 import org.jline.reader.Completer;
@@ -195,8 +192,6 @@ public class CLIMain {
                 .nifiClientFactory(niFiClientFactory)
                 .nifiRegistryClientFactory(nifiRegClientFactory)
                 .interactive(isInteractive)
-                .resultWriter(ResultType.SIMPLE, new SimpleResultWriter())
-                .resultWriter(ResultType.JSON, new JsonResultWriter())
                 .build();
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Command.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Command.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Command.java
index 1a21a8b..3dffcf7 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Command.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Command.java
@@ -22,7 +22,7 @@ import org.apache.commons.cli.Options;
 /**
  * Represents a command to execute.
  */
-public interface Command {
+public interface Command<R extends Result> {
 
     /**
      * Called directly after instantiation of the given command before any other method is called.
@@ -57,7 +57,20 @@ public interface Command {
      * Executes the command with the given CLI params.
      *
      * @param cli the parsed CLI for the command
+     * @return the Result of the command
      */
-    void execute(CommandLine cli) throws CommandException;
+    R execute(CommandLine cli) throws CommandException;
+
+    /**
+     * @return the implementation class of the result
+     */
+    Class<R> getResultImplType();
+
+    /**
+     * @return true if the type of result produced is considered Referenceable
+     */
+    default boolean isReferencable() {
+        return Referenceable.class.isAssignableFrom(getResultImplType());
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Context.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Context.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Context.java
index 14b5186..3053f78 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Context.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Context.java
@@ -36,6 +36,4 @@ public interface Context {
 
     boolean isInteractive();
 
-    ResultWriter getResultWriter(ResultType resultType);
-
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ReferenceResolver.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ReferenceResolver.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ReferenceResolver.java
new file mode 100644
index 0000000..de0feb8
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ReferenceResolver.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.api;
+
+/**
+ * An object that is capable of resolving a positional reference to some value that corresponds with the reference.
+ */
+public interface ReferenceResolver {
+
+    /**
+     * Resolves the passed in positional reference to it's corresponding value.
+     *
+     * @param position a position in this back reference
+     * @return the resolved value for the given position
+     */
+    String resolve(Integer position);
+
+    /**
+     * @return true if the there are no references to resolve, false otherwise
+     */
+    boolean isEmpty();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Referenceable.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Referenceable.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Referenceable.java
new file mode 100644
index 0000000..af44152
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Referenceable.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.api;
+
+/**
+ * An object that is capable of producing a ReferenceResolver.
+ */
+public interface Referenceable {
+
+    /**
+     * @return a ReferenceResolver for this Referenceable
+     */
+    ReferenceResolver createReferenceResolver(Context context);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Result.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Result.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Result.java
new file mode 100644
index 0000000..751f9d3
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/Result.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.api;
+
+/**
+ * A result returned from a command.
+ *
+ * @param <T> the type of result
+ */
+public interface Result<T> {
+
+    /**
+     * @return the result of a command
+     */
+    T getResult();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ResultWriter.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ResultWriter.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ResultWriter.java
deleted file mode 100644
index b7b0741..0000000
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/ResultWriter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.toolkit.cli.api;
-
-import org.apache.nifi.registry.authorization.CurrentUser;
-import org.apache.nifi.registry.bucket.Bucket;
-import org.apache.nifi.registry.flow.VersionedFlow;
-import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
-import org.apache.nifi.web.api.entity.CurrentUserEntity;
-import org.apache.nifi.web.api.entity.ProcessGroupEntity;
-import org.apache.nifi.web.api.entity.RegistryClientsEntity;
-import org.apache.nifi.web.api.entity.VariableRegistryEntity;
-import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
-import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataSetEntity;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.List;
-
-/**
- * Responsible for writing entities to the given stream.
- */
-public interface ResultWriter {
-
-    void writeBuckets(List<Bucket> buckets, PrintStream output) throws IOException;
-
-    void writeBucket(Bucket bucket, PrintStream output) throws IOException;
-
-    void writeFlows(List<VersionedFlow> versionedFlows, PrintStream output) throws IOException;
-
-    void writeFlow(VersionedFlow versionedFlow, PrintStream output) throws IOException;
-
-    void writeSnapshotMetadata(List<VersionedFlowSnapshotMetadata> versions, PrintStream output) throws IOException;
-
-    void writeSnapshotMetadata(VersionedFlowSnapshotMetadata version, PrintStream output) throws IOException;
-
-    void writeRegistryClients(RegistryClientsEntity clientsEntity, PrintStream output) throws IOException;
-
-    void writeVariables(VariableRegistryEntity variableRegistryEntity, PrintStream output) throws IOException;
-
-    void writeSnapshotMetadata(VersionedFlowSnapshotMetadataSetEntity versionedFlowSnapshotMetadataSetEntity, PrintStream output) throws IOException;
-
-    void writeVersionControlInfo(VersionControlInformationEntity versionControlInformationEntity, PrintStream output) throws IOException;
-
-    void writeProcessGroups(List<ProcessGroupEntity> processGroupEntities, PrintStream output) throws IOException;
-
-    void writeCurrentUser(CurrentUserEntity currentUserEntity, PrintStream output) throws IOException;
-
-    void writeCurrentUser(CurrentUser currentUser, PrintStream output) throws IOException;
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/WritableResult.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/WritableResult.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/WritableResult.java
new file mode 100644
index 0000000..c827447
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/api/WritableResult.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.api;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * A result that can be written to a PrintStream.
+ *
+ * @param <T> the type of result
+ */
+public interface WritableResult<T> extends Result<T> {
+
+    /**
+     * Writes this result to the given output stream.
+     *
+     * @param output the output stream
+     * @throws IOException if an error occurs writing the result
+     */
+    void write(PrintStream output) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiRegistryClientFactory.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiRegistryClientFactory.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiRegistryClientFactory.java
index cb12edf..a8d1608 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiRegistryClientFactory.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiRegistryClientFactory.java
@@ -97,7 +97,6 @@ public class NiFiRegistryClientFactory implements ClientFactory<NiFiRegistryClie
 
         // if a proxied entity was specified then return a wrapped client, otherwise return the regular client
         if (!StringUtils.isBlank(proxiedEntity)) {
-            System.out.println("Creating client for proxied entity: " + proxiedEntity);
             return new ProxiedNiFiRegistryClient(client, proxiedEntity);
         } else {
             return client;

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
index 05cc27d..144680c 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
@@ -24,8 +24,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 import org.apache.nifi.toolkit.cli.api.Command;
 import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.api.Result;
 import org.apache.nifi.toolkit.cli.api.ResultType;
-import org.apache.nifi.toolkit.cli.api.ResultWriter;
 
 import java.io.PrintStream;
 import java.io.PrintWriter;
@@ -34,37 +34,47 @@ import java.util.Properties;
 /**
  * Base class for all commands.
  */
-public abstract class AbstractCommand implements Command {
+public abstract class AbstractCommand<R extends Result> implements Command<R> {
 
     private final String name;
+    private final Class<R> resultClass;
     private final Options options;
 
     private Context context;
     private PrintStream output;
 
-    public AbstractCommand(final String name) {
+    public AbstractCommand(final String name, final Class<R> resultClass) {
         this.name = name;
+        this.resultClass = resultClass;
         Validate.notNull(this.name);
+        Validate.notNull(this.resultClass);
 
-        this.options = new Options();
+        this.options = createBaseOptions();
+        Validate.notNull(this.options);
+    }
+
+    protected Options createBaseOptions() {
+        final Options options = new Options();
+
+        options.addOption(CommandOption.URL.createOption());
+        options.addOption(CommandOption.PROPERTIES.createOption());
 
-        this.options.addOption(CommandOption.URL.createOption());
-        this.options.addOption(CommandOption.PROPERTIES.createOption());
+        options.addOption(CommandOption.KEYSTORE.createOption());
+        options.addOption(CommandOption.KEYSTORE_TYPE.createOption());
+        options.addOption(CommandOption.KEYSTORE_PASSWORD.createOption());
+        options.addOption(CommandOption.KEY_PASSWORD.createOption());
 
-        this.options.addOption(CommandOption.KEYSTORE.createOption());
-        this.options.addOption(CommandOption.KEYSTORE_TYPE.createOption());
-        this.options.addOption(CommandOption.KEYSTORE_PASSWORD.createOption());
-        this.options.addOption(CommandOption.KEY_PASSWORD.createOption());
+        options.addOption(CommandOption.TRUSTSTORE.createOption());
+        options.addOption(CommandOption.TRUSTSTORE_TYPE.createOption());
+        options.addOption(CommandOption.TRUSTSTORE_PASSWORD.createOption());
 
-        this.options.addOption(CommandOption.TRUSTSTORE.createOption());
-        this.options.addOption(CommandOption.TRUSTSTORE_TYPE.createOption());
-        this.options.addOption(CommandOption.TRUSTSTORE_PASSWORD.createOption());
+        options.addOption(CommandOption.PROXIED_ENTITY.createOption());
 
-        this.options.addOption(CommandOption.PROXIED_ENTITY.createOption());
+        options.addOption(CommandOption.OUTPUT_TYPE.createOption());
+        options.addOption(CommandOption.VERBOSE.createOption());
+        options.addOption(CommandOption.HELP.createOption());
 
-        this.options.addOption(CommandOption.OUTPUT_TYPE.createOption());
-        this.options.addOption(CommandOption.VERBOSE.createOption());
-        this.options.addOption(CommandOption.HELP.createOption());
+        return options;
     }
 
     @Override
@@ -89,11 +99,16 @@ public abstract class AbstractCommand implements Command {
     }
 
     @Override
-    public String getName() {
+    public final String getName() {
         return name;
     }
 
     @Override
+    public final Class<R> getResultImplType() {
+        return resultClass;
+    }
+
+    @Override
     public Options getOptions() {
         return options;
     }
@@ -116,6 +131,11 @@ public abstract class AbstractCommand implements Command {
         hf.printWrapped(printWriter, width, getDescription());
         hf.printWrapped(printWriter, width, "");
 
+        if (isReferencable()) {
+            hf.printWrapped(printWriter, width, "PRODUCES BACK-REFERENCES");
+            hf.printWrapped(printWriter, width, "");
+        }
+
         hf.printHelp(printWriter, hf.getWidth(), getName(), null, getOptions(),
                 hf.getLeftPadding(), hf.getDescPadding(), null, false);
 
@@ -136,11 +156,6 @@ public abstract class AbstractCommand implements Command {
         output.println();
     }
 
-    protected ResultWriter getResultWriter(final Properties properties) {
-        final ResultType resultType = getResultType(properties);
-        return context.getResultWriter(resultType);
-    }
-
     protected ResultType getResultType(final Properties properties) {
         final ResultType resultType;
         if (properties.containsKey(CommandOption.OUTPUT_TYPE.getLongName())) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommandGroup.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommandGroup.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommandGroup.java
index 531c025..40a95da 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommandGroup.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommandGroup.java
@@ -81,6 +81,11 @@ public abstract class AbstractCommandGroup implements CommandGroup {
                 hf.printWrapped(printWriter, width, "");
                 hf.printWrapped(printWriter, width, "- " + c.getDescription());
                 hf.printWrapped(printWriter, width, "");
+
+                if (c.isReferencable()) {
+                    hf.printWrapped(printWriter, width, "PRODUCES BACK-REFERENCES");
+                    hf.printWrapped(printWriter, width, "");
+                }
             });
 
             printWriter.flush();

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractPropertyCommand.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractPropertyCommand.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractPropertyCommand.java
index fb4dc7f..fca901c 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractPropertyCommand.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractPropertyCommand.java
@@ -20,8 +20,9 @@ import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Result;
 import org.apache.nifi.toolkit.cli.api.Session;
-import org.apache.nifi.toolkit.cli.impl.session.SessionVariables;
+import org.apache.nifi.toolkit.cli.impl.session.SessionVariable;
 
 import java.io.FileInputStream;
 import java.io.InputStream;
@@ -30,14 +31,14 @@ import java.util.Properties;
 /**
  * Base class for commands that support loading properties from the session or an argument.
  */
-public abstract class AbstractPropertyCommand extends AbstractCommand {
+public abstract class AbstractPropertyCommand<R extends Result> extends AbstractCommand<R> {
 
-    public AbstractPropertyCommand(String name) {
-        super(name);
+    public AbstractPropertyCommand(final String name, final Class<R> resultClass) {
+        super(name, resultClass);
     }
 
     @Override
-    public void execute(final CommandLine commandLine) throws CommandException {
+    public final R execute(final CommandLine commandLine) throws CommandException {
         try {
             final Properties properties = new Properties();
 
@@ -51,7 +52,7 @@ public abstract class AbstractPropertyCommand extends AbstractCommand {
                 }
             } else {
                 // no properties file was specified so see if there is anything in the session
-                final SessionVariables sessionVariable = getPropertiesSessionVariable();
+                final SessionVariable sessionVariable = getPropertiesSessionVariable();
                 if (sessionVariable != null) {
                     final Session session = getContext().getSession();
                     final String sessionPropsFiles = session.get(sessionVariable.getVariableName());
@@ -70,7 +71,7 @@ public abstract class AbstractPropertyCommand extends AbstractCommand {
             }
 
             // delegate to sub-classes
-            doExecute(properties);
+            return doExecute(properties);
 
         } catch (CommandException ce) {
             throw ce;
@@ -80,16 +81,17 @@ public abstract class AbstractPropertyCommand extends AbstractCommand {
     }
 
     /**
-     * @return the SessionVariables that specifies the properties file for this command, or null if not supported
+     * @return the SessionVariable that specifies the properties file for this command, or null if not supported
      */
-    protected abstract SessionVariables getPropertiesSessionVariable();
+    protected abstract SessionVariable getPropertiesSessionVariable();
 
     /**
      * Sub-classes implement specific command logic.
      *
      * @param properties the properties which represent the arguments
-     * @throws CommandException if an error occurrs
+     * @return the Result of executing the command
+     * @throws CommandException if an error occurs
      */
-    protected abstract void doExecute(final Properties properties) throws CommandException;
+    public abstract R doExecute(final Properties properties) throws CommandException;
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandFactory.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandFactory.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandFactory.java
index aec0ae4..c3dd791 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandFactory.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandFactory.java
@@ -19,6 +19,7 @@ package org.apache.nifi.toolkit.cli.impl.command;
 import org.apache.nifi.toolkit.cli.api.Command;
 import org.apache.nifi.toolkit.cli.api.CommandGroup;
 import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.command.composite.DemoCommandGroup;
 import org.apache.nifi.toolkit.cli.impl.command.misc.Exit;
 import org.apache.nifi.toolkit.cli.impl.command.misc.Help;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.NiFiCommandGroup;
@@ -55,6 +56,7 @@ public class CommandFactory {
         final List<CommandGroup> groups = new ArrayList<>();
         groups.add(new NiFiRegistryCommandGroup());
         groups.add(new NiFiCommandGroup());
+        groups.add(new DemoCommandGroup());
         groups.add(new SessionCommandGroup());
 
         final Map<String,CommandGroup> groupMap = new TreeMap<>();

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
index cf325a1..9990b22 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
@@ -30,6 +30,9 @@ public enum CommandOption {
     PROPERTIES("p", "properties", "A properties file to load arguments from, " +
             "command line values will override anything in the properties file, must contain full path to file", true),
 
+    NIFI_PROPS("nifiProps", "nifiProps", "A properties file to load for NiFi config", true),
+    NIFI_REG_PROPS("nifiRegProps", "nifiRegProps", "A properties file to load for NiFi Registry config", true),
+
     // Registry - Buckets
     BUCKET_ID("b", "bucketIdentifier", "A bucket identifier", true),
     BUCKET_NAME("bn", "bucketName", "A bucket name", true),

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandProcessor.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandProcessor.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandProcessor.java
index 6cf3114..98fc5f8 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandProcessor.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandProcessor.java
@@ -25,21 +25,30 @@ import org.apache.commons.lang3.Validate;
 import org.apache.nifi.toolkit.cli.api.Command;
 import org.apache.nifi.toolkit.cli.api.CommandGroup;
 import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.api.ReferenceResolver;
+import org.apache.nifi.toolkit.cli.api.Referenceable;
+import org.apache.nifi.toolkit.cli.api.Result;
+import org.apache.nifi.toolkit.cli.api.WritableResult;
 
 import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Takes the arguments from the shell and executes the appropriate command, or prints appropriate usage.
  */
 public class CommandProcessor {
 
+    public static final String BACK_REF_INDICATOR = "&";
+
     private final Map<String,Command> topLevelCommands;
     private final Map<String,CommandGroup> commandGroups;
     private final Context context;
     private final PrintStream out;
 
+    private final AtomicReference<ReferenceResolver> backReferenceHolder = new AtomicReference<>(null);
+
     public CommandProcessor(final Map<String,Command> topLevelCommands, final Map<String,CommandGroup> commandGroups, final Context context) {
         this.topLevelCommands = topLevelCommands;
         this.commandGroups = commandGroups;
@@ -67,12 +76,17 @@ public class CommandProcessor {
         out.println();
 
         commandGroups.entrySet().stream().forEach(e -> e.getValue().printUsage(verbose));
-        out.println("-------------------------------------------------------------------------------");
+        if (verbose) {
+            out.println("-------------------------------------------------------------------------------");
+        }
         topLevelCommands.keySet().stream().forEach(k -> out.println("\t" + k));
         out.println();
     }
 
-    private CommandLine parseCli(Command command, String[] args) throws ParseException {
+    private CommandLine parseCli(final Command command, final String[] args) throws ParseException {
+        // resolve any back-references so the CommandLine ends up with the resolved values in the Options
+        resolveBackReferences(args);
+
         final Options options = command.getOptions();
         final CommandLineParser parser = new DefaultParser();
         final CommandLine commandLine = parser.parse(options, args);
@@ -85,6 +99,42 @@ public class CommandProcessor {
         return commandLine;
     }
 
+    /**
+     * Finds any args that indicate a back-reference and replaces the value of the arg with the
+     * resolved back-reference.
+     *
+     * If the reference does not resolve, or non-numeric position is given, then the arg is left unchanged.
+     *
+     * @param args the args to process
+     */
+    private void resolveBackReferences(final String[] args) {
+        final ReferenceResolver referenceResolver = backReferenceHolder.get();
+        if (referenceResolver == null) {
+            return;
+        }
+
+        for (int i=0; i < args.length; i++) {
+            final String arg = args[i];
+            if (arg == null || !arg.startsWith(BACK_REF_INDICATOR)) {
+                continue;
+            }
+
+            if (context.isInteractive()) {
+                context.getOutput().println();
+            }
+
+            try {
+                final Integer pos = Integer.valueOf(arg.substring(1));
+                final String resolvedReference = referenceResolver.resolve(pos);
+                if (resolvedReference != null) {
+                    args[i] = resolvedReference;
+                }
+            } catch (Exception e) {
+                // skip
+            }
+        }
+    }
+
     public void process(String[] args) {
         if (args == null || args.length == 0) {
             printBasicUsage(null);
@@ -123,20 +173,7 @@ public class CommandProcessor {
                 return;
             }
 
-            try {
-                if (otherArgs.length == 1 && CommandOption.HELP.getLongName().equalsIgnoreCase(otherArgs[0])) {
-                    command.printUsage(null);
-                } else {
-                    command.execute(commandLine);
-                }
-            } catch (Exception e) {
-                command.printUsage(e.getMessage());
-                if (commandLine.hasOption(CommandOption.VERBOSE.getLongName())) {
-                    out.println();
-                    e.printStackTrace(out);
-                    out.println();
-                }
-            }
+            processCommand(otherArgs, commandLine, command);
 
         } catch (Exception e) {
             out.println();
@@ -172,20 +209,7 @@ public class CommandProcessor {
                 return;
             }
 
-            try {
-                if (otherArgs.length == 1 && CommandOption.HELP.getLongName().equalsIgnoreCase(otherArgs[0])) {
-                    command.printUsage(null);
-                } else {
-                    command.execute(commandLine);
-                }
-            } catch (Exception e) {
-                command.printUsage(e.getMessage());
-                if (commandLine.hasOption(CommandOption.VERBOSE.getLongName())) {
-                    out.println();
-                    e.printStackTrace(out);
-                    out.println();
-                }
-            }
+            processCommand(otherArgs, commandLine, command);
 
         } catch (Exception e) {
             out.println();
@@ -194,5 +218,39 @@ public class CommandProcessor {
         }
     }
 
+    private void processCommand(final String[] args, final CommandLine commandLine, final Command command) {
+        try {
+            if (args.length == 1 && CommandOption.HELP.getLongName().equalsIgnoreCase(args[0])) {
+                command.printUsage(null);
+            } else {
+                final Result result = command.execute(commandLine);
+
+                if (result instanceof WritableResult) {
+                    final WritableResult writableResult = (WritableResult) result;
+                    writableResult.write(out);
+                }
+
+                // if the Result is Referenceable then create the resolver and store it in the holder for the next command
+                if (result instanceof Referenceable) {
+                    final Referenceable referenceable = (Referenceable) result;
+                    final ReferenceResolver referenceResolver = referenceable.createReferenceResolver(context);
+
+                    // only set the resolve if its not empty so that a resolver that was already in there sticks around
+                    // and can be used again if the current command didn't produce anything to resolve
+                    if (!referenceResolver.isEmpty()) {
+                        backReferenceHolder.set(referenceResolver);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            command.printUsage(e.getMessage());
+            if (commandLine.hasOption(CommandOption.VERBOSE.getLongName())) {
+                out.println();
+                e.printStackTrace(out);
+                out.println();
+            }
+        }
+    }
+
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/AbstractCompositeCommand.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/AbstractCompositeCommand.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/AbstractCompositeCommand.java
new file mode 100644
index 0000000..1c1e8db
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/AbstractCompositeCommand.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.nifi.toolkit.cli.impl.command.composite;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.toolkit.cli.api.ClientFactory;
+import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Result;
+import org.apache.nifi.toolkit.cli.api.Session;
+import org.apache.nifi.toolkit.cli.api.SessionException;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.AbstractCommand;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.session.SessionVariable;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * Base class for higher-level marco commands that interact with NiFi & Registry to perform a series of actions.
+ */
+public abstract class AbstractCompositeCommand<R extends Result> extends AbstractCommand<R> {
+
+    public AbstractCompositeCommand(final String name, final Class<R> resultClass) {
+        super(name, resultClass);
+    }
+
+    @Override
+    protected final Options createBaseOptions() {
+        final Options options = new Options();
+        options.addOption(CommandOption.NIFI_PROPS.createOption());
+        options.addOption(CommandOption.NIFI_REG_PROPS.createOption());
+        options.addOption(CommandOption.VERBOSE.createOption());
+        return options;
+    }
+
+    @Override
+    public final R execute(final CommandLine cli) throws CommandException {
+        try {
+            final Properties nifiProperties = createProperties(cli, CommandOption.NIFI_PROPS, SessionVariable.NIFI_CLIENT_PROPS);
+            if (nifiProperties == null) {
+                throw new CommandException("Unable to find NiFi config, must specify --"
+                        + CommandOption.NIFI_PROPS.getLongName() + ", or setup session config");
+            }
+
+            final ClientFactory<NiFiClient> nifiClientFactory = getContext().getNiFiClientFactory();
+            final NiFiClient nifiClient = nifiClientFactory.createClient(nifiProperties);
+
+            final Properties registryProperties = createProperties(cli, CommandOption.NIFI_REG_PROPS, SessionVariable.NIFI_REGISTRY_CLIENT_PROPS);
+            if (registryProperties == null) {
+                throw new CommandException("Unable to find NiFi Registry config, must specify --"
+                        + CommandOption.NIFI_REG_PROPS.getLongName() + ", or setup session config");
+            }
+
+            final ClientFactory<NiFiRegistryClient> registryClientFactory = getContext().getNiFiRegistryClientFactory();
+            final NiFiRegistryClient  registryClient = registryClientFactory.createClient(registryProperties);
+
+            return doExecute(cli, nifiClient, nifiProperties, registryClient, registryProperties);
+        } catch (CommandException ce) {
+            throw ce;
+        } catch (Exception e) {
+            throw new CommandException("Error executing command '" + getName() + "' : " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Creates a Properties instance by looking at the propertOption and falling back to the session.
+     *
+     * @param commandLine the current command line
+     * @param propertyOption the options specifying a properties to load
+     * @param sessionVariable the session variable specifying a properties file
+     * @return a Properties instance or null if the option wasn't specified and nothing is in the session
+     */
+    private Properties createProperties(final CommandLine commandLine, final CommandOption propertyOption, final SessionVariable sessionVariable)
+            throws IOException, SessionException {
+
+        // use the properties file specified by the properyOption if it exists
+        if (commandLine.hasOption(propertyOption.getLongName())) {
+            final String propertiesFile = commandLine.getOptionValue(propertyOption.getLongName());
+            if (!StringUtils.isBlank(propertiesFile)) {
+                try (final InputStream in = new FileInputStream(propertiesFile)) {
+                    final Properties properties = new Properties();
+                    properties.load(in);
+                    return properties;
+                }
+            }
+        } else {
+            // no properties file was specified so see if there is anything in the session
+            if (sessionVariable != null) {
+                final Session session = getContext().getSession();
+                final String sessionPropsFiles = session.get(sessionVariable.getVariableName());
+                if (!StringUtils.isBlank(sessionPropsFiles)) {
+                    try (final InputStream in = new FileInputStream(sessionPropsFiles)) {
+                        final Properties properties = new Properties();
+                        properties.load(in);
+                        return properties;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sub-classes implement specific logic using both clients.
+     */
+    public abstract R doExecute(final CommandLine commandLine,
+                                final NiFiClient nifiClient,
+                                final Properties nifiProperties,
+                                final NiFiRegistryClient nifiRegistryClient,
+                                final Properties nifiRegistryProperties)
+            throws CommandException, IOException, NiFiRegistryException, ParseException, NiFiClientException;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/DemoCommandGroup.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/DemoCommandGroup.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/DemoCommandGroup.java
new file mode 100644
index 0000000..3dbfea3
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/DemoCommandGroup.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.impl.command.composite;
+
+import org.apache.nifi.toolkit.cli.api.Command;
+import org.apache.nifi.toolkit.cli.impl.command.AbstractCommandGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Command group for quick demo commands.
+ */
+public class DemoCommandGroup extends AbstractCommandGroup {
+
+    public static final String NAME = "demo";
+
+    public DemoCommandGroup() {
+        super(NAME);
+    }
+
+    @Override
+    protected List<Command> createCommands() {
+        final List<Command> commands = new ArrayList<>();
+        commands.add(new QuickImport());
+        return commands;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/QuickImport.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/QuickImport.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/QuickImport.java
new file mode 100644
index 0000000..5c504a4
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/composite/QuickImport.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.toolkit.cli.impl.command.composite;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGImport;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.registry.CreateRegistryClient;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.registry.GetRegistryClientId;
+import org.apache.nifi.toolkit.cli.impl.command.registry.bucket.CreateBucket;
+import org.apache.nifi.toolkit.cli.impl.command.registry.bucket.ListBuckets;
+import org.apache.nifi.toolkit.cli.impl.command.registry.flow.CreateFlow;
+import org.apache.nifi.toolkit.cli.impl.command.registry.flow.ImportFlowVersion;
+import org.apache.nifi.toolkit.cli.impl.result.BucketsResult;
+import org.apache.nifi.toolkit.cli.impl.result.RegistryClientIDResult;
+import org.apache.nifi.toolkit.cli.impl.result.StringResult;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Properties;
+
+/**
+ * Command to demonstrate a quick import capability.
+ */
+public class QuickImport extends AbstractCompositeCommand<StringResult> {
+
+    public static final String BUCKET_NAME = "Quick Import";
+    public static final String BUCKET_DESC = "Created to demonstrate quickly importing a flow with NiFi CLI.";
+
+    public static final String FLOW_NAME = "Quick Import - ";
+    public static final String FLOW_DESC = "Automatically imported on ";
+
+    public static final String REG_CLIENT_NAME = "Quick Import";
+    public static final String REG_CLIENT_DESC = "Automatically created on ";
+
+    private final ListBuckets listBuckets;
+    private final CreateBucket createBucket;
+    private final CreateFlow createFlow;
+    private final ImportFlowVersion importFlowVersion;
+    private final GetRegistryClientId getRegistryClientId;
+    private final CreateRegistryClient createRegistryClient;
+    private final PGImport pgImport;
+
+    public QuickImport() {
+        super("quick-import", StringResult.class);
+        this.listBuckets = new ListBuckets();
+        this.createBucket = new CreateBucket();
+        this.createFlow = new CreateFlow();
+        this.importFlowVersion = new ImportFlowVersion();
+        this.getRegistryClientId = new GetRegistryClientId();
+        this.createRegistryClient = new CreateRegistryClient();
+        this.pgImport = new PGImport();
+    }
+
+    @Override
+    public String getDescription() {
+        return "Imports a flow from a file or a public URL into a pre-defined bucket named '" + BUCKET_NAME + "'. This command will " +
+                "create the bucket if it doesn't exist, and will create a new flow for each execution. The flow will then be imported " +
+                "to the given NiFi instance.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        // add additional options
+        addOption(CommandOption.INPUT_SOURCE.createOption());
+
+        // initialize sub-commands since we are managing their lifecycle ourselves here
+        listBuckets.initialize(context);
+        createBucket.initialize(context);
+        createFlow.initialize(context);
+        importFlowVersion.initialize(context);
+        getRegistryClientId.initialize(context);
+        createRegistryClient.initialize(context);
+        pgImport.initialize(context);
+    }
+
+    @Override
+    public StringResult doExecute(final CommandLine cli, final NiFiClient nifiClient, final Properties nifiProps,
+                                  final NiFiRegistryClient registryClient, final Properties registryProps)
+            throws IOException, NiFiRegistryException, ParseException, NiFiClientException {
+
+        final boolean isInteractive = getContext().isInteractive();
+
+        // determine the registry client in NiFi to use, or create one
+        // do this first so that we don't get through creating buckets, flows, etc, and then fail on the reg client
+        final String registryClientBaseUrl = registryProps.getProperty(CommandOption.URL.getLongName());
+        final String registryClientId = getRegistryClientId(nifiClient, registryClientBaseUrl, isInteractive);
+
+        // get or create the quick import bucket
+        final String quickImportBucketId = getQuickImportBucketId(registryClient, isInteractive);
+
+        // create a new flow in the quick-import bucket
+        final String quickImportFlowId = createQuickImportFlow(registryClient, quickImportBucketId, isInteractive);
+
+        // import the versioned flow snapshot into newly created quick-import flow
+        final String inputSource = cli.getOptionValue(CommandOption.INPUT_SOURCE.getLongName());
+        if (StringUtils.isBlank(inputSource)) {
+            throw new MissingOptionException("Missing required option --" + CommandOption.INPUT_SOURCE.getLongName());
+        }
+
+        final String quickImportFlowVersion = importFlowVersion(registryClient, quickImportFlowId, isInteractive, inputSource);
+
+        // pg-import to nifi
+        final Properties pgImportProps = new Properties();
+        pgImportProps.setProperty(CommandOption.REGISTRY_CLIENT_ID.getLongName(), registryClientId);
+        pgImportProps.setProperty(CommandOption.BUCKET_ID.getLongName(), quickImportBucketId);
+        pgImportProps.setProperty(CommandOption.FLOW_ID.getLongName(), quickImportFlowId);
+        pgImportProps.setProperty(CommandOption.FLOW_VERSION.getLongName(), quickImportFlowVersion);
+
+        final StringResult createdPgResult = pgImport.doExecute(nifiClient, pgImportProps);
+
+        if (isInteractive) {
+            println();
+            println("Imported process group to NiFi...");
+            println();
+        }
+
+        return createdPgResult;
+    }
+
+    private String importFlowVersion(final NiFiRegistryClient registryClient, final String quickImportFlowId, final boolean isInteractive, final String inputSource)
+            throws ParseException, IOException, NiFiRegistryException {
+        final Properties importVersionProps = new Properties();
+        importVersionProps.setProperty(CommandOption.FLOW_ID.getLongName(), quickImportFlowId);
+        importVersionProps.setProperty(CommandOption.INPUT_SOURCE.getLongName(), inputSource);
+
+        final StringResult createdVersion = importFlowVersion.doExecute(registryClient, importVersionProps);
+        final String quickImportFlowVersion = createdVersion.getResult();
+
+        if (isInteractive) {
+            println();
+            println("Imported flow version...");
+        }
+        return quickImportFlowVersion;
+    }
+
+    private String createQuickImportFlow(final NiFiRegistryClient registryClient, final String quickImportBucketId, final boolean isInteractive)
+            throws ParseException, IOException, NiFiRegistryException {
+        final String flowName = FLOW_NAME + System.currentTimeMillis();
+        final String flowDescription = FLOW_DESC + (new Date()).toString();
+
+        final Properties createFlowProps = new Properties();
+        createFlowProps.setProperty(CommandOption.FLOW_NAME.getLongName(), flowName);
+        createFlowProps.setProperty(CommandOption.FLOW_DESC.getLongName(), flowDescription);
+        createFlowProps.setProperty(CommandOption.BUCKET_ID.getLongName(), quickImportBucketId);
+
+        final StringResult createdFlow = createFlow.doExecute(registryClient, createFlowProps);
+        final String quickImportFlowId = createdFlow.getResult();
+
+        if (isInteractive) {
+            println();
+            println("Created new flow '" + flowName + "'...");
+        }
+        return quickImportFlowId;
+    }
+
+    private String getQuickImportBucketId(final NiFiRegistryClient registryClient, final boolean isInteractive)
+            throws IOException, NiFiRegistryException, MissingOptionException {
+
+        final BucketsResult bucketsResult = listBuckets.doExecute(registryClient, new Properties());
+
+        final Bucket quickImportBucket = bucketsResult.getResult().stream()
+                .filter(b -> BUCKET_NAME.equals(b.getName()))
+                .findFirst().orElse(null);
+
+        // if it doesn't exist, then create the quick import bucket
+        String quickImportBucketId = null;
+        if (quickImportBucket != null) {
+            quickImportBucketId = quickImportBucket.getIdentifier();
+            if (isInteractive) {
+                println();
+                println("Found existing bucket '" + BUCKET_NAME + "'...");
+            }
+        } else {
+            final Properties createBucketProps = new Properties();
+            createBucketProps.setProperty(CommandOption.BUCKET_NAME.getLongName(), BUCKET_NAME);
+            createBucketProps.setProperty(CommandOption.BUCKET_DESC.getLongName(), BUCKET_DESC);
+
+            final StringResult createdBucketId = createBucket.doExecute(registryClient, createBucketProps);
+            quickImportBucketId = createdBucketId.getResult();
+            if (isInteractive) {
+                println();
+                println("Created new bucket '" + BUCKET_NAME + "'...");
+            }
+        }
+        return quickImportBucketId;
+    }
+
+    private String getRegistryClientId(final NiFiClient nifiClient, final String registryClientBaseUrl, final boolean isInteractive)
+            throws NiFiClientException, IOException, MissingOptionException {
+
+        final Properties getRegClientProps = new Properties();
+        getRegClientProps.setProperty(CommandOption.REGISTRY_CLIENT_URL.getLongName(), registryClientBaseUrl);
+
+        String registryClientId;
+        try {
+            final RegistryClientIDResult registryClientResult = getRegistryClientId.doExecute(nifiClient, getRegClientProps);
+            registryClientId = registryClientResult.getResult().getId();
+            if (isInteractive) {
+                println();
+                println("Found existing registry client '" + registryClientResult.getResult().getName() + "'...");
+            }
+        } catch (Exception e) {
+            registryClientId = null;
+        }
+
+        if (registryClientId == null) {
+            final Properties createRegClientProps = new Properties();
+            createRegClientProps.setProperty(CommandOption.REGISTRY_CLIENT_NAME.getLongName(), REG_CLIENT_NAME);
+            createRegClientProps.setProperty(CommandOption.REGISTRY_CLIENT_DESC.getLongName(), REG_CLIENT_DESC + new Date().toString());
+            createRegClientProps.setProperty(CommandOption.REGISTRY_CLIENT_URL.getLongName(), registryClientBaseUrl);
+
+            final StringResult createdRegClient = createRegistryClient.doExecute(nifiClient, createRegClientProps);
+            registryClientId = createdRegClient.getResult();
+
+            if (isInteractive) {
+                println();
+                println("Created new registry client '" + REG_CLIENT_NAME + "'...");
+            }
+        }
+
+        return registryClientId;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Exit.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Exit.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Exit.java
index 974f7f0..c8c6166 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Exit.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Exit.java
@@ -19,13 +19,13 @@ package org.apache.nifi.toolkit.cli.impl.command.misc;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
 import org.apache.nifi.toolkit.cli.api.Command;
-import org.apache.nifi.toolkit.cli.api.CommandException;
 import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
 
 /**
  * Command for exiting the shell.
  */
-public class Exit implements Command {
+public class Exit implements Command<VoidResult> {
 
     @Override
     public void initialize(final Context context) {
@@ -53,8 +53,14 @@ public class Exit implements Command {
     }
 
     @Override
-    public void execute(final CommandLine cli) throws CommandException {
+    public VoidResult execute(final CommandLine cli) {
         System.exit(0);
+        return VoidResult.getInstance();
+    }
+
+    @Override
+    public Class<VoidResult> getResultImplType() {
+        return VoidResult.class;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Help.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Help.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Help.java
index 0544089..76440e2 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Help.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/misc/Help.java
@@ -19,13 +19,13 @@ package org.apache.nifi.toolkit.cli.impl.command.misc;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
 import org.apache.nifi.toolkit.cli.api.Command;
-import org.apache.nifi.toolkit.cli.api.CommandException;
 import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
 
 /**
  * Place-holder so "help" shows up in top-level commands.
  */
-public class Help implements Command {
+public class Help implements Command<VoidResult> {
 
     @Override
     public void initialize(final Context context) {
@@ -53,8 +53,12 @@ public class Help implements Command {
     }
 
     @Override
-    public void execute(final CommandLine cli) throws CommandException {
-        // nothing to do
+    public VoidResult execute(final CommandLine cli) {
+        return VoidResult.getInstance();
     }
 
+    @Override
+    public Class<VoidResult> getResultImplType() {
+        return VoidResult.class;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/AbstractNiFiCommand.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/AbstractNiFiCommand.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/AbstractNiFiCommand.java
index cfe3e53..4cf95db 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/AbstractNiFiCommand.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/AbstractNiFiCommand.java
@@ -19,10 +19,11 @@ package org.apache.nifi.toolkit.cli.impl.command.nifi;
 import org.apache.commons.cli.MissingOptionException;
 import org.apache.nifi.toolkit.cli.api.ClientFactory;
 import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Result;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.command.AbstractPropertyCommand;
-import org.apache.nifi.toolkit.cli.impl.session.SessionVariables;
+import org.apache.nifi.toolkit.cli.impl.session.SessionVariable;
 import org.apache.nifi.web.api.dto.RevisionDTO;
 
 import java.io.IOException;
@@ -31,22 +32,22 @@ import java.util.Properties;
 /**
  * Base class for all NiFi commands.
  */
-public abstract class AbstractNiFiCommand extends AbstractPropertyCommand {
+public abstract class AbstractNiFiCommand<R extends Result> extends AbstractPropertyCommand<R> {
 
-    public AbstractNiFiCommand(final String name) {
-        super(name);
+    public AbstractNiFiCommand(final String name, final Class<R> resultClass) {
+        super(name, resultClass);
     }
 
     @Override
-    protected SessionVariables getPropertiesSessionVariable() {
-        return SessionVariables.NIFI_CLIENT_PROPS;
+    protected SessionVariable getPropertiesSessionVariable() {
+        return SessionVariable.NIFI_CLIENT_PROPS;
     }
 
     @Override
-    protected void doExecute(final Properties properties) throws CommandException {
+    public final R doExecute(final Properties properties) throws CommandException {
         final ClientFactory<NiFiClient> clientFactory = getContext().getNiFiClientFactory();
         try (final NiFiClient client = clientFactory.createClient(properties)) {
-            doExecute(client, properties);
+            return doExecute(client, properties);
         } catch (Exception e) {
             throw new CommandException("Error executing command '" + getName() + "' : " + e.getMessage(), e);
         }
@@ -57,8 +58,9 @@ public abstract class AbstractNiFiCommand extends AbstractPropertyCommand {
      *
      * @param client a NiFi client
      * @param properties properties for the command
+     * @return the Result of executing the command
      */
-    protected abstract void doExecute(final NiFiClient client, final Properties properties)
+    public abstract R doExecute(final NiFiClient client, final Properties properties)
             throws NiFiClientException, IOException, MissingOptionException, CommandException;
 
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CurrentUser.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CurrentUser.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CurrentUser.java
index 4c09510..3f21dea 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CurrentUser.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CurrentUser.java
@@ -16,11 +16,12 @@
  */
 package org.apache.nifi.toolkit.cli.impl.command.nifi.flow;
 
-import org.apache.nifi.toolkit.cli.api.ResultWriter;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.FlowClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.CurrentUserEntityResult;
+import org.apache.nifi.web.api.entity.CurrentUserEntity;
 
 import java.io.IOException;
 import java.util.Properties;
@@ -28,10 +29,10 @@ import java.util.Properties;
 /**
  * Command to get information about the current user accessing the NiFi instance.
  */
-public class CurrentUser extends AbstractNiFiCommand {
+public class CurrentUser extends AbstractNiFiCommand<CurrentUserEntityResult> {
 
     public CurrentUser() {
-        super("current-user");
+        super("current-user", CurrentUserEntityResult.class);
     }
 
     @Override
@@ -41,10 +42,10 @@ public class CurrentUser extends AbstractNiFiCommand {
     }
 
     @Override
-    protected void doExecute(NiFiClient client, Properties properties)
+    public CurrentUserEntityResult doExecute(NiFiClient client, Properties properties)
             throws NiFiClientException, IOException {
         final FlowClient flowClient = client.getFlowClient();
-        final ResultWriter resultWriter = getResultWriter(properties);
-        resultWriter.writeCurrentUser(flowClient.getCurrentUser(), getContext().getOutput());
+        final CurrentUserEntity currentUserEntity = flowClient.getCurrentUser();
+        return new CurrentUserEntityResult(getResultType(properties), currentUserEntity);
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetRootId.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetRootId.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetRootId.java
index bf1fb75..be27f33 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetRootId.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetRootId.java
@@ -20,6 +20,7 @@ import org.apache.nifi.toolkit.cli.impl.client.nifi.FlowClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.StringResult;
 
 import java.io.IOException;
 import java.util.Properties;
@@ -27,10 +28,10 @@ import java.util.Properties;
 /**
  * Returns the id of the root process group of the given NiFi instance.
  */
-public class GetRootId extends AbstractNiFiCommand {
+public class GetRootId extends AbstractNiFiCommand<StringResult> {
 
     public GetRootId() {
-        super("get-root-id");
+        super("get-root-id", StringResult.class);
     }
 
     @Override
@@ -39,10 +40,10 @@ public class GetRootId extends AbstractNiFiCommand {
     }
 
     @Override
-    protected void doExecute(final NiFiClient client, final Properties properties)
+    public StringResult doExecute(final NiFiClient client, final Properties properties)
             throws NiFiClientException, IOException {
         final FlowClient flowClient = client.getFlowClient();
-        println(flowClient.getRootGroupId());
+        return new StringResult(flowClient.getRootGroupId());
     }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGChangeVersion.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGChangeVersion.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGChangeVersion.java
index 87d7845..cf332b0 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGChangeVersion.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGChangeVersion.java
@@ -25,6 +25,7 @@ import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.VersionsClient;
 import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
 import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
 import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
 import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataEntity;
@@ -37,10 +38,10 @@ import java.util.Properties;
 /**
  * Command to change the version of a version controlled process group.
  */
-public class PGChangeVersion extends AbstractNiFiCommand {
+public class PGChangeVersion extends AbstractNiFiCommand<VoidResult> {
 
     public PGChangeVersion() {
-        super("pg-change-version");
+        super("pg-change-version", VoidResult.class);
     }
 
     @Override
@@ -57,7 +58,7 @@ public class PGChangeVersion extends AbstractNiFiCommand {
     }
 
     @Override
-    protected void doExecute(final NiFiClient client, final Properties properties)
+    public VoidResult doExecute(final NiFiClient client, final Properties properties)
             throws NiFiClientException, IOException, MissingOptionException, CommandException {
         final String pgId = getRequiredArg(properties, CommandOption.PG_ID);
 
@@ -117,6 +118,7 @@ public class PGChangeVersion extends AbstractNiFiCommand {
             versionsClient.deleteUpdateRequest(updateRequestId);
         }
 
+        return VoidResult.getInstance();
     }
 
     private int getLatestVersion(final NiFiClient client, final VersionControlInformationDTO existingVersionControlDTO)

http://git-wip-us.apache.org/repos/asf/nifi/blob/b68eebd4/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetAllVersions.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetAllVersions.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetAllVersions.java
index b26dffd..b3cd36c 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetAllVersions.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetAllVersions.java
@@ -17,15 +17,14 @@
 package org.apache.nifi.toolkit.cli.impl.command.nifi.pg;
 
 import org.apache.commons.cli.MissingOptionException;
-import org.apache.nifi.toolkit.cli.api.CommandException;
 import org.apache.nifi.toolkit.cli.api.Context;
-import org.apache.nifi.toolkit.cli.api.ResultWriter;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.FlowClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.VersionsClient;
 import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.VersionedFlowSnapshotMetadataSetResult;
 import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
 import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
 import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataSetEntity;
@@ -36,10 +35,10 @@ import java.util.Properties;
 /**
  * Command to get all the available versions for a given process group that is under version control.
  */
-public class PGGetAllVersions extends AbstractNiFiCommand {
+public class PGGetAllVersions extends AbstractNiFiCommand<VersionedFlowSnapshotMetadataSetResult> {
 
     public PGGetAllVersions() {
-        super("pg-get-all-versions");
+        super("pg-get-all-versions", VersionedFlowSnapshotMetadataSetResult.class);
     }
     @Override
     public String getDescription() {
@@ -52,8 +51,9 @@ public class PGGetAllVersions extends AbstractNiFiCommand {
     }
 
     @Override
-    protected void doExecute(final NiFiClient client, final Properties properties)
-            throws NiFiClientException, IOException, MissingOptionException, CommandException {
+    public VersionedFlowSnapshotMetadataSetResult doExecute(final NiFiClient client, final Properties properties)
+            throws NiFiClientException, IOException, MissingOptionException {
+
         final String pgId = getRequiredArg(properties, CommandOption.PG_ID);
 
         final VersionsClient versionsClient = client.getVersionsClient();
@@ -75,8 +75,7 @@ public class PGGetAllVersions extends AbstractNiFiCommand {
             throw new NiFiClientException("No versions available");
         }
 
-        final ResultWriter resultWriter = getResultWriter(properties);
-        resultWriter.writeSnapshotMetadata(versions, getContext().getOutput());
+        return new VersionedFlowSnapshotMetadataSetResult(getResultType(properties), versions);
     }
 
 }