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

nizhikov pushed a commit to branch IGNITE-19676
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit d79ad50c3aee2374220102b2582a00788f8d1f05
Author: nizhikov <nizhi...@apache.org>
AuthorDate: Tue Jun 13 12:05:38 2023 +0300

    IGNITE-19676 JMX Command invoker
---
 .../internal/commandline/ArgumentParser.java       | 133 +-------------
 .../internal/commandline/CommandHandler.java       | 105 +++++------
 .../internal/commandline/CommandInvoker.java       | 189 +++++++-------------
 .../java/org/apache/ignite/internal/IgniteEx.java  |   7 +
 .../org/apache/ignite/internal/IgniteKernal.java   |  10 ++
 .../internal/management/ActivateCommand.java       |   3 +-
 .../internal/management/DeactivateCommand.java     |   3 +-
 .../internal/management/SetStateCommand.java       |   3 +-
 .../ignite/internal/management/StateCommand.java   |   7 +-
 .../management/api/AbstractCommandInvoker.java     | 115 +++++++++++++
 .../internal/management/api/CommandUtils.java      | 131 ++++++++++++++
 .../internal/management/api/LocalCommand.java      |   4 +-
 .../api/{LocalCommand.java => Node.java}           |  33 +++-
 .../management/api/NodeCommandInvoker.java         | 124 +++++++++++++
 .../management/cache/CacheListCommand.java         |   2 +-
 .../consistency/ConsistencyRepairCommand.java      |   6 +-
 .../internal/management/jmx/CommandMBean.java      | 191 +++++++++++++++++++++
 .../management/jmx/JmxComandRegistryInvoker.java   | 117 +++++++++++++
 .../JmxCommandRegistryInvokerPluginProvider.java   | 110 ++++++++++++
 .../internal/management/tx/TxInfoCommand.java      |   3 +-
 .../apache/ignite/internal/util/IgniteUtils.java   |  23 +++
 21 files changed, 994 insertions(+), 325 deletions(-)

diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
index 3a0a58ee016..20ab97269cb 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
@@ -19,7 +19,6 @@
 package org.apache.ignite.internal.commandline;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -30,8 +29,6 @@ import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
-import java.util.function.Function;
-import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.internal.client.GridClientConfiguration;
@@ -41,13 +38,9 @@ import 
org.apache.ignite.internal.dto.IgniteDataTransferObject;
 import org.apache.ignite.internal.management.IgniteCommandRegistry;
 import org.apache.ignite.internal.management.api.Argument;
 import org.apache.ignite.internal.management.api.ArgumentGroup;
-import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
 import org.apache.ignite.internal.management.api.CliSubcommandsWithPrefix;
 import org.apache.ignite.internal.management.api.Command;
 import org.apache.ignite.internal.management.api.CommandsRegistry;
-import org.apache.ignite.internal.management.api.ComputeCommand;
-import org.apache.ignite.internal.management.api.HelpCommand;
-import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.Positional;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -65,13 +58,13 @@ import static 
org.apache.ignite.internal.commandline.argument.parser.CLIArgument
 import static 
org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.NAME_PREFIX;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
+import static org.apache.ignite.internal.management.api.CommandUtils.argument;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.asOptional;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.executable;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.fromFormattedCommandName;
 import static org.apache.ignite.internal.management.api.CommandUtils.isBoolean;
-import static 
org.apache.ignite.internal.management.api.CommandUtils.parameterExample;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedFieldName;
-import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedNames;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
 import static org.apache.ignite.ssl.SslContextFactory.DFLT_SSL_PROTOCOL;
 
@@ -339,10 +332,7 @@ public class ArgumentParser {
             iter.remove();
         }
 
-        if (!(cmd instanceof ComputeCommand)
-            && !(cmd instanceof LocalCommand)
-            && !(cmd instanceof BeforeNodeStartCommand)
-            && !(cmd instanceof HelpCommand)) {
+        if (!executable(cmd)) {
             throw new IllegalArgumentException(
                 "Command " + toFormattedCommandName(cmd.getClass()) + " can't 
be executed"
             );
@@ -394,121 +384,4 @@ public class ArgumentParser {
 
         return F.t(positionalArgs, namedArgs);
     }
-
-    /**
-     * Fill and vaildate command argument.
-     *
-     * @param argCls Argument class.
-     * @param positionalParamProvider Provider of positional parameters.
-     * @param paramProvider Provider of named parameters.
-     * @return Argument filled with parameters.
-     * @param <A> Argument type.
-     */
-    private static <A extends IgniteDataTransferObject> A argument(
-        Class<A> argCls,
-        BiFunction<Field, Integer, Object> positionalParamProvider,
-        Function<Field, Object> paramProvider
-    ) {
-        try {
-            ArgumentState<A> arg = new ArgumentState<>(argCls);
-
-            visitCommandParams(
-                argCls,
-                fld -> arg.accept(fld, positionalParamProvider.apply(fld, 
arg.nextIdx())),
-                fld -> arg.accept(fld, paramProvider.apply(fld)),
-                (argGrp, flds) -> flds.forEach(fld -> {
-                    if (fld.isAnnotationPresent(Positional.class))
-                        arg.accept(fld, positionalParamProvider.apply(fld, 
arg.nextIdx()));
-                    else
-                        arg.accept(fld, paramProvider.apply(fld));
-                })
-            );
-
-            if (arg.argGrp != null && (!arg.grpOptional() && 
!arg.grpFldExists))
-                throw new IllegalArgumentException("One of " + 
toFormattedNames(argCls, arg.grpdFlds) + " required");
-
-            return arg.res;
-        }
-        catch (InstantiationException | IllegalAccessException e) {
-            throw new IgniteException(e);
-        }
-    }
-
-    /** */
-    private static class ArgumentState<A extends IgniteDataTransferObject> 
implements BiConsumer<Field, Object> {
-        /** */
-        final A res;
-
-        /** */
-        final ArgumentGroup argGrp;
-
-        /** */
-        boolean grpFldExists;
-
-        /** */
-        int idx;
-
-        /** */
-        final Set<String> grpdFlds;
-
-        /** */
-        public ArgumentState(Class<A> argCls) throws InstantiationException, 
IllegalAccessException {
-            res = argCls.newInstance();
-            argGrp = argCls.getAnnotation(ArgumentGroup.class);
-            grpdFlds = argGrp == null
-                ? Collections.emptySet()
-                : new HashSet<>(Arrays.asList(argGrp.value()));
-        }
-
-        /** */
-        public boolean grpOptional() {
-            return argGrp == null || argGrp.optional();
-        }
-
-        /** */
-        private int nextIdx() {
-            int idx0 = idx;
-
-            idx++;
-
-            return idx0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void accept(Field fld, Object val) {
-            boolean grpdFld = grpdFlds.contains(fld.getName());
-
-            if (val == null) {
-                if (grpdFld || fld.getAnnotation(Argument.class).optional())
-                    return;
-
-                String name = fld.isAnnotationPresent(Positional.class)
-                    ? parameterExample(fld, false)
-                    : toFormattedFieldName(fld);
-
-                throw new IllegalArgumentException("Argument " + name + " 
required.");
-            }
-
-            if (grpdFld) {
-                if (grpFldExists && (argGrp != null && argGrp.onlyOneOf())) {
-                    throw new IllegalArgumentException(
-                        "Only one of " + toFormattedNames(res.getClass(), 
grpdFlds) + " allowed"
-                    );
-                }
-
-                grpFldExists = true;
-            }
-
-            try {
-                res.getClass().getMethod(fld.getName(), 
fld.getType()).invoke(res, val);
-            }
-            catch (NoSuchMethodException | IllegalAccessException e) {
-                throw new IgniteException(e);
-            }
-            catch (InvocationTargetException e) {
-                if (e.getTargetException() != null && e.getTargetException() 
instanceof RuntimeException)
-                    throw (RuntimeException)e.getTargetException();
-            }
-        }
-    }
 }
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
index 318a4121c55..3a90fe80654 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
@@ -54,10 +54,8 @@ import 
org.apache.ignite.internal.management.api.CliSubcommandsWithPrefix;
 import org.apache.ignite.internal.management.api.Command;
 import org.apache.ignite.internal.management.api.CommandUtils;
 import org.apache.ignite.internal.management.api.CommandsRegistry;
-import org.apache.ignite.internal.management.api.ComputeCommand;
 import org.apache.ignite.internal.management.api.EnumDescription;
 import org.apache.ignite.internal.management.api.HelpCommand;
-import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.Positional;
 import org.apache.ignite.internal.management.cache.CacheCommand;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -90,6 +88,7 @@ import static 
org.apache.ignite.internal.management.api.CommandUtils.NAME_PREFIX
 import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.asOptional;
 import static org.apache.ignite.internal.management.api.CommandUtils.cmdText;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.executable;
 import static org.apache.ignite.internal.management.api.CommandUtils.join;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.parameterExample;
 import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName;
@@ -255,66 +254,75 @@ public class CommandHandler {
 
             commandName = 
toFormattedCommandName(args.root().getClass()).toUpperCase();
 
-            CommandInvoker<A> invoker = new CommandInvoker<>(args.command(), 
args.commandArg(), getClientConfiguration(args));
+            try (CommandInvoker<A> invoker = new CommandInvoker<>(
+                args.command(),
+                args.commandArg(),
+                getClientConfiguration(args)
+            )) {
+                int tryConnectMaxCount = 3;
 
-            int tryConnectMaxCount = 3;
+                boolean suppliedAuth = !F.isEmpty(args.userName()) && 
!F.isEmpty(args.password());
 
-            boolean suppliedAuth = !F.isEmpty(args.userName()) && 
!F.isEmpty(args.password());
+                boolean credentialsRequested = false;
 
-            boolean credentialsRequested = false;
-
-            while (true) {
-                try {
-                    if (!invoker.prepare(logger))
-                        return EXIT_CODE_OK;
+                while (true) {
+                    try {
+                        if (!invoker.prepare(logger::info))
+                            return EXIT_CODE_OK;
 
-                    if (!args.autoConfirmation()) {
-                        if (!confirm(invoker.confirmationPrompt())) {
-                            logger.info("Operation cancelled.");
+                        if (!args.autoConfirmation()) {
+                            if (!confirm(invoker.confirmationPrompt())) {
+                                logger.info("Operation cancelled.");
 
-                            return EXIT_CODE_OK;
+                                return EXIT_CODE_OK;
+                            }
                         }
-                    }
 
-                    logger.info("Command [" + commandName + "] started");
-                    logger.info("Arguments: " + argumentsToString(rawArgs));
-                    logger.info(U.DELIM);
+                        logger.info("Command [" + commandName + "] started");
+                        logger.info("Arguments: " + 
argumentsToString(rawArgs));
+                        logger.info(U.DELIM);
 
-                    if (args.command() instanceof HelpCommand)
-                        printUsage(logger, args.root());
-                    else if (args.command() instanceof BeforeNodeStartCommand)
-                        lastOperationRes = 
invoker.invokeBeforeNodeStart(logger);
-                    else
-                        lastOperationRes = invoker.invoke(logger, 
args.verbose());
+                        String deprecationMsg = 
args.command().deprecationMessage(args.commandArg());
 
-                    break;
-                }
-                catch (Throwable e) {
-                    if (!isAuthError(e))
-                        throw e;
+                        if (deprecationMsg != null)
+                            logger.warning(deprecationMsg);
 
-                    if (suppliedAuth)
-                        throw new GridClientAuthenticationException("Wrong 
credentials.");
+                        if (args.command() instanceof HelpCommand)
+                            printUsage(logger, args.root());
+                        else if (args.command() instanceof 
BeforeNodeStartCommand)
+                            lastOperationRes = 
invoker.invokeBeforeNodeStart(logger::info);
+                        else
+                            lastOperationRes = invoker.invoke(logger::info, 
args.verbose());
 
-                    if (tryConnectMaxCount == 0) {
-                        throw new GridClientAuthenticationException("Maximum 
number of " +
-                            "retries exceeded");
+                        break;
                     }
+                    catch (Throwable e) {
+                        if (!isAuthError(e))
+                            throw e;
+
+                        if (suppliedAuth)
+                            throw new GridClientAuthenticationException("Wrong 
credentials.");
 
-                    logger.info(credentialsRequested ?
-                        "Authentication error, please try again." :
-                        "This cluster requires authentication.");
+                        if (tryConnectMaxCount == 0) {
+                            throw new 
GridClientAuthenticationException("Maximum number of " +
+                                "retries exceeded");
+                        }
 
-                    if (credentialsRequested)
-                        tryConnectMaxCount--;
+                        logger.info(credentialsRequested ?
+                            "Authentication error, please try again." :
+                            "This cluster requires authentication.");
 
-                    invoker.clientConfiguration(getClientConfiguration(
-                        retrieveUserName(args, invoker.clientConfiguration()),
-                        new String(requestPasswordFromConsole("password: ")),
-                        args
-                    ));
+                        if (credentialsRequested)
+                            tryConnectMaxCount--;
 
-                    credentialsRequested = true;
+                        invoker.clientConfiguration(getClientConfiguration(
+                            retrieveUserName(args, 
invoker.clientConfiguration()),
+                            new String(requestPasswordFromConsole("password: 
")),
+                            args
+                        ));
+
+                        credentialsRequested = true;
+                    }
                 }
             }
 
@@ -827,10 +835,7 @@ public class CommandHandler {
      * @param logger Logger to print help to.
      */
     public static void usage(Command<?, ?> cmd, List<Command<?, ?>> parents, 
IgniteLogger logger) {
-        if (cmd instanceof LocalCommand
-            || cmd instanceof ComputeCommand
-            || cmd instanceof HelpCommand
-            || cmd instanceof BeforeNodeStartCommand) {
+        if (executable(cmd)) {
             logger.info("");
 
             if (cmd.getClass().isAnnotationPresent(IgniteExperimental.class))
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java
index 06c66b3e015..e2b1d65fbd3 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java
@@ -23,9 +23,9 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientBeforeNodeStart;
 import org.apache.ignite.internal.client.GridClientCompute;
@@ -35,112 +35,40 @@ import 
org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.client.GridClientFactory;
 import org.apache.ignite.internal.client.GridClientNode;
 import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.AbstractCommandInvoker;
 import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
 import org.apache.ignite.internal.management.api.Command;
-import org.apache.ignite.internal.management.api.ComputeCommand;
-import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.PreparableCommand;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.visor.VisorTaskArgument;
 import org.apache.ignite.lang.IgniteBiTuple;
 
-import static java.util.Collections.singleton;
 import static java.util.stream.Collectors.toMap;
 import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
 
 /**
  * Adapter of new management API command for legacy {@code control.sh} 
execution flow.
  */
-public class CommandInvoker<A extends IgniteDataTransferObject> {
-    /** Command to execute. */
-    private final Command<A, ?> cmd;
-
-    /** Parsed argument. */
-    private final A arg;
+public class CommandInvoker<A extends IgniteDataTransferObject> extends 
AbstractCommandInvoker<A> {
 
     /** Client configuration. */
     private GridClientConfiguration clientCfg;
 
+    /** Client. */
+    private GridClient client;
+
     /** @param cmd Command to execute. */
     public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration 
clientCfg) {
-        this.cmd = cmd;
-        this.arg = arg;
+        super(cmd, arg);
         this.clientCfg = clientCfg;
     }
 
-    /**
-     * Actual command execution with verbose mode if needed.
-     * Implement it if your command supports verbose mode.
-     *
-     * @param log Logger to use.
-     * @param verbose Use verbose mode or not
-     * @return Result of operation (mostly usable for tests).
-     * @throws Exception If error occur.
-     */
-    public <R> R invoke(IgniteLogger log, boolean verbose) throws Exception {
-        try (GridClient client = startClient(clientCfg)) {
-            String deprecationMsg = cmd.deprecationMessage(arg);
-
-            if (deprecationMsg != null)
-                log.warning(deprecationMsg);
-
-            R res;
-
-            if (cmd instanceof LocalCommand)
-                res = ((LocalCommand<A, R>)cmd).execute(client, arg, 
log::info);
-            else if (cmd instanceof ComputeCommand) {
-                GridClientCompute compute = client.compute();
-
-                Map<UUID, GridClientNode> nodes = compute.nodes().stream()
-                    .collect(toMap(GridClientNode::nodeId, n -> n));
-
-                ComputeCommand<A, R> cmd = (ComputeCommand<A, R>)this.cmd;
-
-                Collection<UUID> cmdNodes = cmd.nodes(nodes, arg);
-
-                if (cmdNodes == null)
-                    cmdNodes = singleton(defaultNode(client, 
clientCfg).nodeId());
-
-                for (UUID id : cmdNodes) {
-                    if (!nodes.containsKey(id))
-                        throw new IllegalArgumentException("Node with id=" + 
id + " not found.");
-                }
-
-                Collection<GridClientNode> connectable = F.viewReadOnly(
-                    cmdNodes,
-                    nodes::get,
-                    id -> nodes.get(id).connectable()
-                );
-
-                if (!F.isEmpty(connectable))
-                    compute = compute.projection(connectable);
-
-                res = compute.execute(cmd.taskClass().getName(), new 
VisorTaskArgument<>(cmdNodes, arg, false));
-
-                cmd.printResult(arg, res, log::info);
-            }
-            else
-                throw new IllegalArgumentException("Unknown command type: " + 
cmd);
-
-            return res;
-        }
-        catch (Throwable e) {
-            log.error("Failed to perform operation.");
-            log.error(CommandLogger.errorMessage(e));
-
-            throw e;
-        }
-    }
-
     /** */
-    public boolean prepare(IgniteLogger log) throws Exception {
+    public boolean prepare(Consumer<String> printer) throws Exception {
         if (!(cmd instanceof PreparableCommand))
             return true;
 
-        try (GridClient client = startClient(clientCfg)) {
-            return ((PreparableCommand<A, ?>)cmd).prepare(client, arg, 
log::info);
-        }
+        return ((PreparableCommand<A, ?>)cmd).prepare(client(), arg, printer);
     }
 
     /**
@@ -151,24 +79,21 @@ public class CommandInvoker<A extends 
IgniteDataTransferObject> {
     }
 
     /** */
-    public <R> R invokeBeforeNodeStart(IgniteLogger log) throws Exception {
+    public <R> R invokeBeforeNodeStart(Consumer<String> printer) throws 
Exception {
         try (GridClientBeforeNodeStart client = 
startClientBeforeNodeStart(clientCfg)) {
-            return ((BeforeNodeStartCommand<A, R>)cmd).execute(client, arg, 
log::info);
+            return ((BeforeNodeStartCommand<A, R>)cmd).execute(client, arg, 
printer);
         }
         catch (GridClientDisconnectedException e) {
             throw new GridClientException(e.getCause());
         }
     }
 
-    /**
-     * Method to create thin client for communication with cluster.
-     *
-     * @param clientCfg Thin client configuration.
-     * @return Grid thin client instance which is already connected to cluster.
-     * @throws Exception If error occur.
-     */
-    private static GridClient startClient(GridClientConfiguration clientCfg) 
throws Exception {
-        GridClient client = GridClientFactory.start(clientCfg);
+    /** {@inheritDoc} */
+    @Override protected GridClient client() throws GridClientException {
+        if (client != null && client.connected())
+            return client;
+
+        client = GridClientFactory.start(clientCfg);
 
         // If connection is unsuccessful, fail before doing any operations:
         if (!client.connected()) {
@@ -187,38 +112,14 @@ public class CommandInvoker<A extends 
IgniteDataTransferObject> {
         return client;
     }
 
-    /**
-     * Method to create thin client for communication with node before it 
starts.
-     * If node has already started, there will be an error.
-     *
-     * @param clientCfg Thin client configuration.
-     * @return Grid thin client instance which is already connected to node 
before it starts.
-     * @throws Exception If error occur.
-     */
-    private static GridClientBeforeNodeStart startClientBeforeNodeStart(
-        GridClientConfiguration clientCfg
-    ) throws Exception {
-        GridClientBeforeNodeStart client = 
GridClientFactory.startBeforeNodeStart(clientCfg);
-
-        // If connection is unsuccessful, fail before doing any operations:
-        if (!client.connected()) {
-            GridClientException lastErr = client.checkLastError();
-
-            try {
-                client.close();
-            }
-            catch (Throwable e) {
-                lastErr.addSuppressed(e);
-            }
-
-            throw lastErr;
-        }
-
-        return client;
+    /** {@inheritDoc} */
+    @Override protected Map<UUID, GridClientNode> nodes() throws 
GridClientException {
+        return client().compute().nodes().stream()
+            .collect(toMap(GridClientNode::nodeId, n -> n));
     }
 
-    /** */
-    private static GridClientNode defaultNode(GridClient client, 
GridClientConfiguration clientCfg) throws GridClientException {
+    /** {@inheritDoc} */
+    @Override protected GridClientNode defaultNode() throws 
GridClientException {
         GridClientNode node;
 
         // Prefer node from connect string.
@@ -241,22 +142,52 @@ public class CommandInvoker<A extends 
IgniteDataTransferObject> {
 
             String origAddr = addr.getHostName() + ":" + parts[1];
 
-            node = listHosts(client).filter(tuple -> 
origAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null);
+            node = listHosts(client()).filter(tuple -> 
origAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null);
 
             if (node == null)
-                node = listHostsByClientNode(client).filter(tuple -> 
tuple.get2().size() == 1 && cfgAddr.equals(tuple.get2().get(0))).
+                node = listHostsByClientNode(client()).filter(tuple -> 
tuple.get2().size() == 1 && cfgAddr.equals(tuple.get2().get(0))).
                     findFirst().map(IgniteBiTuple::get1).orElse(null);
         }
         else
-            node = listHosts(client).filter(tuple -> 
cfgAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null);
+            node = listHosts(client()).filter(tuple -> 
cfgAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null);
 
         // Otherwise choose random node.
         if (node == null)
-            node = balancedNode(client.compute());
+            node = balancedNode(client().compute());
 
         return node;
     }
 
+    /**
+     * Method to create thin client for communication with node before it 
starts.
+     * If node has already started, there will be an error.
+     *
+     * @param clientCfg Thin client configuration.
+     * @return Grid thin client instance which is already connected to node 
before it starts.
+     * @throws Exception If error occur.
+     */
+    private static GridClientBeforeNodeStart startClientBeforeNodeStart(
+        GridClientConfiguration clientCfg
+    ) throws Exception {
+        GridClientBeforeNodeStart client = 
GridClientFactory.startBeforeNodeStart(clientCfg);
+
+        // If connection is unsuccessful, fail before doing any operations:
+        if (!client.connected()) {
+            GridClientException lastErr = client.checkLastError();
+
+            try {
+                client.close();
+            }
+            catch (Throwable e) {
+                lastErr.addSuppressed(e);
+            }
+
+            throw lastErr;
+        }
+
+        return client;
+    }
+
     /**
      * @param client Client.
      * @return List of hosts.
@@ -313,4 +244,10 @@ public class CommandInvoker<A extends 
IgniteDataTransferObject> {
     public GridClientConfiguration clientConfiguration() {
         return clientCfg;
     }
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        if (client != null)
+            client.close();
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java 
b/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java
index 5d982290852..b76e916fe65 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java
@@ -26,6 +26,7 @@ import org.apache.ignite.IgniteSet;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.cluster.IgniteClusterEx;
+import org.apache.ignite.internal.management.IgniteCommandRegistry;
 import org.apache.ignite.internal.processors.cache.GridCacheUtilityKey;
 import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
 import org.apache.ignite.lang.IgniteBiTuple;
@@ -172,4 +173,10 @@ public interface IgniteEx extends Ignite {
      * @throws IgniteException If set could not be fetched or created.
      */
     public <T> IgniteSet<T> set(String name, int cacheId, boolean collocated, 
boolean separated) throws IgniteException;
+
+    /**
+     * @return Registry with all management commands known by node.
+     * @see org.apache.ignite.internal.management.api.Command
+     */
+    public IgniteCommandRegistry commands();
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java 
b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
index 39f4ad5be07..d2c11bbd73e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
@@ -94,6 +94,7 @@ import 
org.apache.ignite.internal.cache.query.index.IndexProcessor;
 import org.apache.ignite.internal.cluster.ClusterGroupAdapter;
 import org.apache.ignite.internal.cluster.IgniteClusterEx;
 import org.apache.ignite.internal.maintenance.MaintenanceProcessor;
+import org.apache.ignite.internal.management.IgniteCommandRegistry;
 import org.apache.ignite.internal.managers.GridManager;
 import org.apache.ignite.internal.managers.IgniteMBeansManager;
 import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager;
@@ -398,6 +399,10 @@ public class IgniteKernal implements IgniteEx, 
Externalizable {
     @GridToStringExclude
     private IgniteMBeansManager mBeansMgr;
 
+    /** Registry with all management commands known by node. */
+    @GridToStringExclude
+    private final IgniteCommandRegistry cmdReg = new IgniteCommandRegistry();
+
     /** Ignite configuration instance. */
     private IgniteConfiguration cfg;
 
@@ -3093,6 +3098,11 @@ public class IgniteKernal implements IgniteEx, 
Externalizable {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override public IgniteCommandRegistry commands() {
+        return cmdReg;
+    }
+
     /**
      * The {@code ctx.gateway().readLock()} is used underneath.
      */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java
index 59f7f8c45ae..f45dd759cbf 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.management;
 import java.util.function.Consumer;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientClusterState;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.NoArg;
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
@@ -38,7 +39,7 @@ public class ActivateCommand implements LocalCommand<NoArg, 
NoArg> {
     }
 
     /** {@inheritDoc} */
-    @Override public NoArg execute(GridClient cli, NoArg arg, Consumer<String> 
printer) throws Exception {
+    @Override public NoArg execute(GridClient cli, NoArg arg, Consumer<String> 
printer) throws GridClientException {
         GridClientClusterState state = cli.state();
 
         state.state(ACTIVE, false);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java
index 5b4b938d066..feef7f5a4e3 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.management;
 import java.util.function.Consumer;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientClusterState;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.NoArg;
 import org.apache.ignite.internal.management.api.PreparableCommand;
@@ -45,7 +46,7 @@ public class DeactivateCommand implements 
LocalCommand<DeactivateCommandArg, NoA
     }
 
     /** {@inheritDoc} */
-    @Override public NoArg execute(GridClient cli, DeactivateCommandArg arg, 
Consumer<String> printer) throws Exception {
+    @Override public NoArg execute(GridClient cli, DeactivateCommandArg arg, 
Consumer<String> printer) throws GridClientException {
         GridClientClusterState state = cli.state();
 
         state.state(INACTIVE, arg.force());
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java
index e03a7e0ea98..f25ea3c0078 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.management;
 import java.util.function.Consumer;
 import org.apache.ignite.cluster.ClusterState;
 import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.management.api.LocalCommand;
 
 /** */
@@ -35,7 +36,7 @@ public class SetStateCommand implements 
LocalCommand<SetStateCommandArg, Boolean
     }
 
     /** {@inheritDoc} */
-    @Override public Boolean execute(GridClient cli, SetStateCommandArg arg, 
Consumer<String> printer) throws Exception {
+    @Override public Boolean execute(GridClient cli, SetStateCommandArg arg, 
Consumer<String> printer) throws GridClientException {
         ClusterState clusterState = cli.state().state();
 
         if (clusterState == arg.state()) {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java
index 4386253affa..af4cb226fa3 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java
@@ -22,6 +22,7 @@ import java.util.function.Consumer;
 import org.apache.ignite.cluster.ClusterState;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientClusterState;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.api.NoArg;
 import org.apache.ignite.internal.util.lang.GridTuple3;
@@ -41,7 +42,11 @@ public class StateCommand implements LocalCommand<NoArg, 
GridTuple3<UUID, String
     }
 
     /** {@inheritDoc} */
-    @Override public GridTuple3<UUID, String, ClusterState> execute(GridClient 
cli, NoArg arg, Consumer<String> printer) throws Exception {
+    @Override public GridTuple3<UUID, String, ClusterState> execute(
+        GridClient cli,
+        NoArg arg,
+        Consumer<String> printer
+    ) throws GridClientException {
         GridClientClusterState state = cli.state();
 
         printer.accept("Cluster  ID: " + state.id());
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java
new file mode 100644
index 00000000000..6d60e7664bf
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.ignite.internal.management.api;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ *
+ */
+public abstract class AbstractCommandInvoker<A extends 
IgniteDataTransferObject> implements AutoCloseable {
+    /** Command to execute. */
+    protected final Command<A, ?> cmd;
+
+    /** Parsed argument. */
+    protected final A arg;
+
+    /** @param cmd Command to execute. */
+    protected AbstractCommandInvoker(Command<A, ?> cmd, A arg) {
+        this.cmd = cmd;
+        this.arg = arg;
+    }
+
+    /**
+     * Actual command execution with verbose mode if needed.
+     * Implement it if your command supports verbose mode.
+     *
+     * @param printer Result printer.
+     * @param verbose Use verbose mode or not
+     * @return Result of operation (mostly usable for tests).
+     * @throws GridClientException In case of error.
+     */
+    public <R> R invoke(Consumer<String> printer, boolean verbose) throws 
GridClientException {
+        R res;
+
+        if (cmd instanceof LocalCommand)
+            res = ((LocalCommand<A, R>)cmd).execute(client(), arg, printer);
+        else if (cmd instanceof ComputeCommand) {
+            GridClientCompute compute = client().compute();
+
+            Map<UUID, GridClientNode> nodes = compute.nodes().stream()
+                .collect(toMap(GridClientNode::nodeId, n -> n));
+
+            ComputeCommand<A, R> cmd = (ComputeCommand<A, R>)this.cmd;
+
+            Collection<UUID> cmdNodes = cmd.nodes(nodes, arg);
+
+            if (cmdNodes == null)
+                cmdNodes = singleton(defaultNode().nodeId());
+
+            for (UUID id : cmdNodes) {
+                if (!nodes.containsKey(id))
+                    throw new IllegalArgumentException("Node with id=" + id + 
" not found.");
+            }
+
+            Collection<GridClientNode> connectable = F.viewReadOnly(
+                cmdNodes,
+                nodes::get,
+                id -> nodes.get(id).connectable()
+            );
+
+            if (!F.isEmpty(connectable))
+                compute = compute.projection(connectable);
+
+            res = compute.execute(cmd.taskClass().getName(), new 
VisorTaskArgument<>(cmdNodes, arg, false));
+
+            cmd.printResult(arg, res, printer);
+        }
+        else
+            throw new IllegalArgumentException("Unknown command type: " + cmd);
+
+        return res;
+    }
+
+    /**
+     * Method to create thin client for communication with cluster.
+     *
+     * @return Grid thin client instance which is already connected to cluster.
+     * @throws GridClientException If error occur.
+     */
+    protected abstract GridClient client() throws GridClientException;
+
+    /** @return Cluster nodes. */
+    protected abstract Map<UUID, GridClientNode> nodes() throws 
GridClientException;
+
+    /** @return Default node to execute commands. */
+    protected abstract GridClientNode defaultNode() throws GridClientException;
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java
index 0c42c174667..3be3e9ad672 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.management.api;
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -31,7 +32,9 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteException;
@@ -411,6 +414,17 @@ public class CommandUtils {
             .collect(Collectors.toList());
     }
 
+    /**
+     * @param cmd Command.
+     * @return {@code True} if command can be executed, {@code false} 
otherwise.
+     */
+    public static boolean executable(Command<?, ?> cmd) {
+        return cmd instanceof LocalCommand
+            || cmd instanceof ComputeCommand
+            || cmd instanceof HelpCommand
+            || cmd instanceof BeforeNodeStartCommand;
+    }
+
     /**
      * Join input parameters with specified {@code delimeter} between them.
      *
@@ -555,4 +569,121 @@ public class CommandUtils {
             throw new NumberFormatException("Can't parse number '" + val + "', 
expected type: " + expectedType.getName());
         }
     }
+
+    /**
+     * Fill and vaildate command argument.
+     *
+     * @param argCls Argument class.
+     * @param positionalParamProvider Provider of positional parameters.
+     * @param paramProvider Provider of named parameters.
+     * @return Argument filled with parameters.
+     * @param <A> Argument type.
+     */
+    public static <A extends IgniteDataTransferObject> A argument(
+        Class<A> argCls,
+        BiFunction<Field, Integer, Object> positionalParamProvider,
+        Function<Field, Object> paramProvider
+    ) {
+        try {
+            ArgumentState<A> arg = new ArgumentState<>(argCls);
+
+            visitCommandParams(
+                argCls,
+                fld -> arg.accept(fld, positionalParamProvider.apply(fld, 
arg.nextIdx())),
+                fld -> arg.accept(fld, paramProvider.apply(fld)),
+                (argGrp, flds) -> flds.forEach(fld -> {
+                    if (fld.isAnnotationPresent(Positional.class))
+                        arg.accept(fld, positionalParamProvider.apply(fld, 
arg.nextIdx()));
+                    else
+                        arg.accept(fld, paramProvider.apply(fld));
+                })
+            );
+
+            if (arg.argGrp != null && (!arg.grpOptional() && 
!arg.grpFldExists))
+                throw new IllegalArgumentException("One of " + 
toFormattedNames(argCls, arg.grpdFlds) + " required");
+
+            return arg.res;
+        }
+        catch (InstantiationException | IllegalAccessException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** */
+    private static class ArgumentState<A extends IgniteDataTransferObject> 
implements BiConsumer<Field, Object> {
+        /** */
+        final A res;
+
+        /** */
+        final ArgumentGroup argGrp;
+
+        /** */
+        boolean grpFldExists;
+
+        /** */
+        int idx;
+
+        /** */
+        final Set<String> grpdFlds;
+
+        /** */
+        public ArgumentState(Class<A> argCls) throws InstantiationException, 
IllegalAccessException {
+            res = argCls.newInstance();
+            argGrp = argCls.getAnnotation(ArgumentGroup.class);
+            grpdFlds = argGrp == null
+                ? Collections.emptySet()
+                : new HashSet<>(Arrays.asList(argGrp.value()));
+        }
+
+        /** */
+        public boolean grpOptional() {
+            return argGrp == null || argGrp.optional();
+        }
+
+        /** */
+        private int nextIdx() {
+            int idx0 = idx;
+
+            idx++;
+
+            return idx0;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void accept(Field fld, Object val) {
+            boolean grpdFld = grpdFlds.contains(fld.getName());
+
+            if (val == null) {
+                if (grpdFld || fld.getAnnotation(Argument.class).optional())
+                    return;
+
+                String name = fld.isAnnotationPresent(Positional.class)
+                    ? parameterExample(fld, false)
+                    : toFormattedFieldName(fld);
+
+                throw new IllegalArgumentException("Argument " + name + " 
required.");
+            }
+
+            if (grpdFld) {
+                if (grpFldExists && (argGrp != null && argGrp.onlyOneOf())) {
+                    throw new IllegalArgumentException(
+                        "Only one of " + toFormattedNames(res.getClass(), 
grpdFlds) + " allowed"
+                    );
+                }
+
+                grpFldExists = true;
+            }
+
+            try {
+                res.getClass().getMethod(fld.getName(), 
fld.getType()).invoke(res, val);
+            }
+            catch (NoSuchMethodException | IllegalAccessException e) {
+                throw new IgniteException(e);
+            }
+            catch (InvocationTargetException e) {
+                if (e.getTargetException() != null && e.getTargetException() 
instanceof RuntimeException)
+                    throw (RuntimeException)e.getTargetException();
+            }
+        }
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
index fc32e932479..044423e7792 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.management.api;
 
 import java.util.function.Consumer;
 import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.dto.IgniteDataTransferObject;
 
 /**
@@ -30,6 +31,7 @@ public interface LocalCommand<A extends 
IgniteDataTransferObject, R> extends Com
      * @param arg Command argument.
      * @param printer Results printer.
      * @return Command result.
+     * @throws GridClientException In case of error.
      */
-    public R execute(GridClient cli, A arg, Consumer<String> printer) throws 
Exception;
+    public R execute(GridClient cli, A arg, Consumer<String> printer) throws 
GridClientException;
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java
similarity index 63%
copy from 
modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
copy to 
modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java
index fc32e932479..90838c533c4 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java
@@ -17,19 +17,34 @@
 
 package org.apache.ignite.internal.management.api;
 
-import java.util.function.Consumer;
-import org.apache.ignite.internal.client.GridClient;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.dto.IgniteDataTransferObject;
 
 /**
- * Command that must be executed directly using {@link GridClient} instance.
+ * Cluster node representation required for management API.
+ *
+ * @see ComputeCommand#nodes(Map, IgniteDataTransferObject)
  */
-public interface LocalCommand<A extends IgniteDataTransferObject, R> extends 
Command<A, R> {
+public interface Node {
+    /**
+     * @see ClusterNode#id()
+     */
+    public UUID id();
+
+    /**
+     * @see ClusterNode#consistentId()
+     */
+    public Object consistentId();
+
+    /**
+     * @see ClusterNode#order()
+     */
+    public long order();
+
     /**
-     * @param cli Grid client instance.
-     * @param arg Command argument.
-     * @param printer Results printer.
-     * @return Command result.
+     * @see ClusterNode#isClient()
      */
-    public R execute(GridClient cli, A arg, Consumer<String> printer) throws 
Exception;
+    public boolean isClient();
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java
new file mode 100644
index 00000000000..a3117017159
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.management.api;
+
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientCacheMode;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.client.GridClientNodeMetrics;
+import org.apache.ignite.internal.client.GridClientProtocol;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public class NodeCommandInvoker<A extends IgniteDataTransferObject> extends 
AbstractCommandInvoker<A> {
+    /** */
+    private final IgniteEx ignite;
+
+    /**
+     * @param cmd Command to execute.
+     * @param arg Argument.
+     * @param ignite Ignite node.
+     */
+    public NodeCommandInvoker(Command<A, ?> cmd, A arg, IgniteEx ignite) {
+        super(cmd, arg);
+        this.ignite = ignite;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected GridClient client() throws GridClientException {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Map<UUID, GridClientNode> nodes() throws 
GridClientException {
+        return ignite.cluster().nodes().stream().map(n -> new GridClientNode() 
{
+            @Override public UUID nodeId() {
+                return n.id();
+            }
+
+            @Override public Object consistentId() {
+                return n.consistentId();
+            }
+
+            @Override public boolean connectable() {
+                return true;
+            }
+
+            @Override public long order() {
+                return n.order();
+            }
+
+            @Override public boolean isClient() {
+                return n.isClient();
+            }
+
+            @Override public List<String> tcpAddresses() {
+                return U.arrayList(n.addresses());
+            }
+
+            @Override public List<String> tcpHostNames() {
+                return U.arrayList(n.hostNames());
+            }
+
+            @Override public int tcpPort() {
+                return -1;
+            }
+
+            @Override public Map<String, Object> attributes() {
+                return n.attributes();
+            }
+
+            @Override public <T> @Nullable T attribute(String name) {
+                return n.attribute(name);
+            }
+
+            @Override public GridClientNodeMetrics metrics() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override public Map<String, GridClientCacheMode> caches() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override public Collection<InetSocketAddress> 
availableAddresses(GridClientProtocol proto, boolean filterResolved) {
+                throw new UnsupportedOperationException();
+            }
+        }).collect(Collectors.toMap(n -> n.nodeId(), Function.identity()));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected GridClientNode defaultNode() throws 
GridClientException {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() throws Exception {
+        // No-op.
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java
index fd27d101e15..c0f34c60d62 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java
@@ -72,7 +72,7 @@ public class CacheListCommand implements 
LocalCommand<CacheListCommandArg, Visor
         GridClient cli,
         CacheListCommandArg arg,
         Consumer<String> printer
-    ) throws Exception {
+    ) throws GridClientException {
         VisorViewCacheCmd cmd = arg.groups()
             ? GROUPS
             : (arg.seq() ? SEQ : CACHES);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java
index 797ca54ce0b..096d4e4dfd3 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java
@@ -22,7 +22,7 @@ import java.util.Collections;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.client.GridClientNode;
@@ -52,7 +52,7 @@ public class ConsistencyRepairCommand implements 
LocalCommand<ConsistencyRepairC
         GridClient cli,
         ConsistencyRepairCommandArg arg,
         Consumer<String> printer
-    ) throws Exception {
+    ) throws GridClientException, IgniteException {
         StringBuilder sb = new StringBuilder();
         boolean failed = false;
 
@@ -74,7 +74,7 @@ public class ConsistencyRepairCommand implements 
LocalCommand<ConsistencyRepairC
         String res = sb.toString();
 
         if (failed)
-            throw new IgniteCheckedException(res);
+            throw new IgniteException(res);
 
         printer.accept(res);
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java
new file mode 100644
index 00000000000..9f9c0bce821
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java
@@ -0,0 +1,191 @@
+/*
+ * 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.ignite.internal.management.jmx;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.ReflectionException;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.Argument;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.CommandUtils;
+import org.apache.ignite.internal.management.api.EnumDescription;
+import org.apache.ignite.internal.management.api.NodeCommandInvoker;
+
+import static javax.management.MBeanOperationInfo.ACTION;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
+
+/** */
+public class CommandMBean<A extends IgniteDataTransferObject> implements 
DynamicMBean {
+    /** */
+    public static final String METHOD = "invoke";
+
+    /** */
+    private final IgniteEx ignite;
+
+    /** */
+    private final Command<A, ?> cmd;
+
+    /** */
+    public CommandMBean(IgniteEx ignite, Command<A, ?> cmd) {
+        this.ignite = ignite;
+        this.cmd = cmd;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getAttribute(
+        String attribute
+    ) throws AttributeNotFoundException, MBeanException, ReflectionException {
+        throw new UnsupportedOperationException("getAttribute");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAttribute(
+        Attribute attribute
+    ) throws AttributeNotFoundException, InvalidAttributeValueException, 
MBeanException, ReflectionException {
+        throw new UnsupportedOperationException("setAttribute");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object invoke(
+        String actionName,
+        Object[] params,
+        String[] signature
+    ) throws MBeanException, ReflectionException {
+        if (!METHOD.equals(actionName))
+            throw new UnsupportedOperationException(actionName);
+
+        try {
+            StringBuilder resStr = new StringBuilder();
+
+            Consumer<String> printer = str -> resStr.append(str).append('\n');
+
+            new NodeCommandInvoker<>(
+                cmd,
+                new ParamsToArgument(params).argument(),
+                ignite
+            ).invoke(printer, false);
+
+            return resStr.toString();
+        }
+        catch (GridClientException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public MBeanInfo getMBeanInfo() {
+        return new MBeanInfo(
+            CommandMBean.class.getName(),
+            cmd.getClass().getSimpleName(),
+            null,
+            null,
+            new MBeanOperationInfo[]{
+                new MBeanOperationInfo(METHOD, cmd.description(), 
argumentsDescription(), String.class.getName(), ACTION)
+            },
+            null
+        );
+    }
+
+    /** */
+    public MBeanParameterInfo[] argumentsDescription() {
+        List<MBeanParameterInfo> args = new ArrayList<>();
+
+        Consumer<Field> fldCnsmr = fld -> {
+            String descStr;
+
+            if (!fld.isAnnotationPresent(EnumDescription.class))
+                descStr = fld.getAnnotation(Argument.class).description();
+            else {
+                EnumDescription enumDesc = 
fld.getAnnotation(EnumDescription.class);
+
+                String[] names = enumDesc.names();
+                String[] descriptions = enumDesc.descriptions();
+
+                StringBuilder bldr = new StringBuilder();
+
+                for (int i = 0; i < names.length; i++) {
+                    if (i != 0)
+                        bldr.append('\n');
+                    bldr.append(names[i]).append(" - 
").append(descriptions[i]);
+                }
+
+                descStr = bldr.toString();
+            }
+
+            args.add(new MBeanParameterInfo(fld.getName(), 
String.class.getName(), descStr));
+        };
+
+        visitCommandParams(cmd.argClass(), fldCnsmr, fldCnsmr, (optional, 
flds) -> flds.forEach(fldCnsmr));
+
+        return args.toArray(new MBeanParameterInfo[args.size()]);
+    }
+
+    /** {@inheritDoc} */
+    @Override public AttributeList getAttributes(String[] attributes) {
+        throw new UnsupportedOperationException("getAttributes");
+    }
+
+    /** {@inheritDoc} */
+    @Override public AttributeList setAttributes(AttributeList attributes) {
+        throw new UnsupportedOperationException("setAttributes");
+    }
+
+    /** */
+    private class ParamsToArgument implements Function<Field, Object> {
+        /** */
+        private int cntr;
+
+        /** */
+        private final Object[] vals;
+
+        /** */
+        private ParamsToArgument(Object[] vals) {
+            this.vals = vals;
+        }
+
+        /** */
+        public A argument() {
+            // This will map vals to argument fields.
+            return CommandUtils.argument(cmd.argClass(), (fld, pos) -> 
apply(fld), this);
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object apply(Field field) {
+            String val = (String)vals[cntr];
+
+            cntr++;
+
+            return val != null ? CommandUtils.parseVal(val, field.getType()) : 
null;
+        }
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java
new file mode 100644
index 00000000000..886581e15d6
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.management.jmx;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.CommandsRegistry;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.plugin.IgnitePlugin;
+import org.apache.ignite.plugin.PluginContext;
+
+import static 
org.apache.ignite.internal.management.api.CommandUtils.executable;
+import static org.apache.ignite.internal.util.IgniteUtils.makeMBeanName;
+
+/** */
+public class JmxComandRegistryInvoker implements IgnitePlugin {
+    /** */
+    private PluginContext ctx;
+
+    /** */
+    private IgniteLogger log;
+
+    /** */
+    private IgniteEx grid;
+
+    /** */
+    private final List<ObjectName> mBeans = new ArrayList<>();
+
+    /** */
+    public void context(PluginContext ctx) {
+        this.ctx = ctx;
+        this.grid = (IgniteEx)ctx.grid();
+        this.log = ctx.log(JmxComandRegistryInvoker.class);
+    }
+
+    /** */
+    public void onIgniteStart() {
+        if (U.IGNITE_MBEANS_DISABLED) {
+            log.info("Plugin disabled, IGNITE_MBEANS_DISABLED = true.");
+
+            return;
+        }
+
+        grid.commands().commands().forEachRemaining(cmd -> 
register(cmd.getKey(), new LinkedList<>(), cmd.getValue()));
+    }
+
+    /** */
+    public void register(String name, List<String> parents, Command<?, ?> cmd) 
{
+        if (cmd instanceof CommandsRegistry) {
+            parents.add(name);
+
+            ((CommandsRegistry<?, ?>)cmd).commands()
+                .forEachRemaining(cmd0 -> register(cmd0.getKey(), parents, 
cmd0.getValue()));
+
+            parents.remove(parents.size() - 1);
+
+            if (!executable(cmd))
+                return;
+        }
+
+        try {
+            ObjectName mbean = U.registerMBean(
+                grid.configuration().getMBeanServer(),
+                makeMBeanName(grid.context().igniteInstanceName(), 
"management", parents, name),
+                new CommandMBean<>(grid, cmd),
+                CommandMBean.class
+            );
+
+            mBeans.add(mbean);
+
+            if (log.isDebugEnabled())
+                log.debug("Command JMX bean created. " + mbean);
+        }
+        catch (JMException e) {
+            log.error("MBean for command '" + name + "' can't be created.", e);
+        }
+    }
+
+    /** */
+    public void onIgniteStop() {
+        MBeanServer jmx = grid.configuration().getMBeanServer();
+
+        for (ObjectName mBean : mBeans) {
+            try {
+                jmx.unregisterMBean(mBean);
+
+                if (log.isDebugEnabled())
+                    log.debug("Unregistered command MBean: " + mBean);
+            }
+            catch (JMException e) {
+                log.error("Failed to unregister command MBean: " + mBean, e);
+            }
+        }
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java
new file mode 100644
index 00000000000..fa9b732af8d
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.ignite.internal.management.jmx;
+
+import java.io.Serializable;
+import java.util.UUID;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.IgniteVersionUtils;
+import org.apache.ignite.plugin.CachePluginContext;
+import org.apache.ignite.plugin.CachePluginProvider;
+import org.apache.ignite.plugin.ExtensionRegistry;
+import org.apache.ignite.plugin.IgnitePlugin;
+import org.apache.ignite.plugin.PluginContext;
+import org.apache.ignite.plugin.PluginProvider;
+import org.apache.ignite.plugin.PluginValidationException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class JmxCommandRegistryInvokerPluginProvider implements PluginProvider 
{
+    /** */
+    private JmxComandRegistryInvoker jmxInvoker = new 
JmxComandRegistryInvoker();
+
+    /** {@inheritDoc} */
+    @Override public String name() {
+        return "JMX invoker of CommandRegistry";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String version() {
+        return IgniteVersionUtils.VER_STR;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String copyright() {
+        return IgniteVersionUtils.COPYRIGHT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgnitePlugin plugin() {
+        return jmxInvoker;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void initExtensions(PluginContext ctx, ExtensionRegistry 
registry) throws IgniteCheckedException {
+        jmxInvoker.context(ctx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CachePluginProvider 
createCacheProvider(CachePluginContext ctx) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onIgniteStart() throws IgniteCheckedException {
+        jmxInvoker.onIgniteStart();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onIgniteStop(boolean cancel) {
+        jmxInvoker.onIgniteStop();
+    }
+
+    /** {@inheritDoc} */
+    @Override public @Nullable Serializable provideDiscoveryData(UUID nodeId) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void receiveDiscoveryData(UUID nodeId, Serializable data) 
{
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validateNewNode(ClusterNode node) throws 
PluginValidationException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object createComponent(PluginContext ctx, Class cls) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start(PluginContext ctx) throws 
IgniteCheckedException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop(boolean cancel) throws IgniteCheckedException {
+        // No-op.
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java
index 71087fe2f3e..c62ebfdbe5d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.function.Consumer;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientException;
 import org.apache.ignite.internal.client.GridClientNode;
 import org.apache.ignite.internal.management.api.LocalCommand;
 import org.apache.ignite.internal.management.tx.TxCommand.AbstractTxCommandArg;
@@ -60,7 +61,7 @@ public class TxInfoCommand implements 
LocalCommand<AbstractTxCommandArg, Map<Clu
         GridClient cli,
         AbstractTxCommandArg arg0,
         Consumer<String> printer
-    ) throws Exception {
+    ) throws GridClientException {
         TxInfoCommandArg arg = (TxInfoCommandArg)arg0;
 
         Optional<GridClientNode> node = cli.compute().nodes().stream()
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index 3bd673b00da..fa65abdadd9 100755
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -5053,6 +5053,26 @@ public abstract class IgniteUtils {
      */
     public static ObjectName makeMBeanName(@Nullable String 
igniteInstanceName, @Nullable String grp, String name)
         throws MalformedObjectNameException {
+        return makeMBeanName(igniteInstanceName, grp, Collections.emptyList(), 
name);
+    }
+
+    /**
+     * Constructs JMX object name with given properties.
+     * Map with ordered {@code groups} used for proper object name 
construction.
+     *
+     * @param igniteInstanceName Ignite instance name.
+     * @param grp Name of the group.
+     * @param grps Names of extended groups.
+     * @param name Name of mbean.
+     * @return JMX object name.
+     * @throws MalformedObjectNameException Thrown in case of any errors.
+     */
+    public static ObjectName makeMBeanName(
+        @Nullable String igniteInstanceName,
+        @Nullable String grp,
+        List<String> grps,
+        String name
+    ) throws MalformedObjectNameException {
         SB sb = new SB(JMX_DOMAIN + ':');
 
         appendClassLoaderHash(sb);
@@ -5065,6 +5085,9 @@ public abstract class IgniteUtils {
         if (grp != null)
             sb.a("group=").a(escapeObjectNameValue(grp)).a(',');
 
+        for (int i = 0; i < grps.size(); i++)
+            sb.a("group").a(i).a("=").a(grps.get(i)).a(',');
+
         sb.a("name=").a(escapeObjectNameValue(name));
 
         return new ObjectName(sb.toString());

Reply via email to