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

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

commit 3ebec825c8096950fc285cb9405aae364bd8d3ce
Author: nizhikov <[email protected]>
AuthorDate: Wed Apr 19 12:00:22 2023 +0300

    IGNITE-15629 One more invoker implemented
---
 modules/openapi-management-ext/pom.xml             | 105 ++++++++
 .../internal/management/openapi/HeaderFilter.java  |  63 +++++
 .../management/openapi/ManagementApiServlet.java   | 123 +++++++++
 .../OpenApiCommandsRegistryInvokerPlugin.java      | 288 +++++++++++++++++++++
 ...enApiCommandsRegistryInvokerPluginProvider.java | 109 ++++++++
 .../management/openapi/OpenApiSelfTest.java        |  41 +++
 pom.xml                                            |   1 +
 7 files changed, 730 insertions(+)

diff --git a/modules/openapi-management-ext/pom.xml 
b/modules/openapi-management-ext/pom.xml
new file mode 100644
index 00000000..4241681e
--- /dev/null
+++ b/modules/openapi-management-ext/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<!--
+    POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent-ext-internal</artifactId>
+        <version>1</version>
+        <relativePath>../../parent-internal/pom.xml</relativePath>
+    </parent>
+
+    <properties>
+        <swagger.version>2.2.9</swagger.version>
+    </properties>
+
+    <artifactId>ignite-openapi-management-ext</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <url>https://ignite.apache.org</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-log4j2</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-spring</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-jaxrs2</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/HeaderFilter.java
 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/HeaderFilter.java
new file mode 100644
index 00000000..e45b6006
--- /dev/null
+++ 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/HeaderFilter.java
@@ -0,0 +1,63 @@
+/*
+ * 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.openapi;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ *
+ */
+public class HeaderFilter implements Filter {
+    /** {@inheritDoc} */
+    @Override public void doFilter(
+        ServletRequest req,
+        ServletResponse resp,
+        FilterChain chain
+    ) throws ServletException, IOException {
+        HttpServletResponse response = (HttpServletResponse) resp;
+        HttpServletRequest request = (HttpServletRequest) req;
+
+        response.setHeader("Access-Control-Allow-Origin", "*");
+        response.setHeader("X-Frame-Options", "SAMEORIGIN");
+        response.setHeader("Access-Control-Allow-Methods", "DELETE, HEAD, GET, 
OPTIONS, POST, PUT");
+        response.setHeader("Access-Control-Max-Age", "3600");
+        response.setHeader("Access-Control-Allow-Credentials", "true");
+        response.setHeader("Access-Control-Allow-Origin", "*");
+        response.setHeader("Access-Control-Allow-Headers", "Content-Type, 
api_key, Authorization ");
+
+        chain.doFilter(req, resp);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init(FilterConfig filterConfig) {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void destroy() {
+        // No-op.
+    }
+}
diff --git 
a/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/ManagementApiServlet.java
 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/ManagementApiServlet.java
new file mode 100644
index 00000000..c07f77d7
--- /dev/null
+++ 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/ManagementApiServlet.java
@@ -0,0 +1,123 @@
+/*
+ * 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.openapi;
+
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+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 javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static 
org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.executeAndPrint;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.formattedName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.fromFormattedName;
+import static org.apache.ignite.internal.management.api.CommandUtils.parseVal;
+import static 
org.apache.ignite.internal.management.openapi.OpenApiCommandsRegistryInvokerPlugin.TEXT_PLAIN;
+
+/** */
+public class ManagementApiServlet extends HttpServlet {
+    /** */
+    private final IgniteEx grid;
+
+    /** */
+    private final String root;
+
+    /** */
+    public ManagementApiServlet(IgniteEx grid, String root) {
+        this.grid = grid;
+        this.root = root;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void doGet(
+        HttpServletRequest req,
+        HttpServletResponse resp
+    ) throws IOException {
+        resp.setStatus(HttpServletResponse.SC_OK);
+        resp.setContentType(TEXT_PLAIN);
+        resp.setCharacterEncoding("UTF-8");
+
+        String uri = req.getRequestURI();
+
+        if (!uri.startsWith(root))
+            throw new IllegalArgumentException("Wrong URI: " + uri);
+
+        String[] cmdPathAndPosArgs = uri.substring(root.length() + 
1).split("/");
+
+        if (cmdPathAndPosArgs.length == 0)
+            throw new IllegalArgumentException("Empty command path: " + uri);
+
+        Command<?, ?, ?> cmd =
+            
grid.context().commands().command(fromFormattedName(cmdPathAndPosArgs[0], 
CMD_WORDS_DELIM));
+
+        if (cmd == null)
+            throw new IllegalArgumentException("Unknown command: " + 
cmdPathAndPosArgs[0]);
+
+        AtomicInteger i = new AtomicInteger(1);
+
+        while (cmd instanceof CommandsRegistry && i.get() < 
cmdPathAndPosArgs.length) {
+            Command<?, ?, ?> cmd0 =
+                
((CommandsRegistry)cmd).command(fromFormattedName(cmdPathAndPosArgs[i.get()], 
CMD_WORDS_DELIM));
+
+            if (cmd0 == null)
+                break;
+
+            cmd = cmd0;
+
+            i.incrementAndGet();
+        }
+
+        parseAndExecute(req, resp, cmdPathAndPosArgs, cmd, i);
+    }
+
+    /** */
+    private <A extends IgniteDataTransferObject> void parseAndExecute(
+        HttpServletRequest req,
+        HttpServletResponse resp,
+        String[] cmdPathAndPosArgs,
+        Command<A, ?, ?> cmd,
+        AtomicInteger i
+    ) throws IOException {
+        try {
+            A arg = CommandUtils.arguments(
+                cmd.args(),
+                (fld, pos) -> i.get() + pos < cmdPathAndPosArgs.length
+                    ? parseVal(cmdPathAndPosArgs[i.get() + pos], fld.getType())
+                    : null,
+                fld -> {
+                    String val = req.getParameter(formattedName(fld.getName(), 
CMD_WORDS_DELIM));
+
+                    return val == null ? null : parseVal(val, fld.getType());
+                }
+            );
+
+            executeAndPrint(grid, cmd, arg, resp.getWriter()::println);
+        }
+        catch (InstantiationException | IllegalAccessException e) {
+            throw new IgniteException(e);
+        }
+    }
+}
diff --git 
a/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPlugin.java
 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPlugin.java
new file mode 100644
index 00000000..73b51d16
--- /dev/null
+++ 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPlugin.java
@@ -0,0 +1,288 @@
+/*
+ * 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.openapi;
+
+import io.swagger.v3.jaxrs2.integration.OpenApiServlet;
+import io.swagger.v3.oas.integration.GenericOpenApiContextBuilder;
+import io.swagger.v3.oas.integration.OpenApiContextLocator;
+import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.OpenApiContext;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.BooleanSchema;
+import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.IntegerSchema;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.NumberSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.media.StringSchema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteVersionUtils;
+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.CommandWithSubs;
+import org.apache.ignite.internal.management.api.CommandsRegistry;
+import org.apache.ignite.internal.management.api.PositionalArgument;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.IgnitePlugin;
+import org.apache.ignite.plugin.PluginContext;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import static 
io.swagger.v3.oas.integration.api.OpenApiContext.OPENAPI_CONTEXT_ID_KEY;
+import static io.swagger.v3.oas.models.parameters.Parameter.StyleEnum.SIMPLE;
+import static java.util.Collections.singletonList;
+import static javax.servlet.DispatcherType.REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.formattedName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.valueExample;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
+
+/**
+ *
+ */
+public class OpenApiCommandsRegistryInvokerPlugin implements IgnitePlugin {
+    /** */
+    public static final String API_CTX_ID = "ignite-management-api-ctx";
+
+    /** */
+    public static final String API_ID = "ignite-management-api";
+
+    /** */
+    public static final String INVOKER_BASE_URL = "/management";
+
+    /** */
+    public static final String TEXT_PLAIN = "text/plain";
+
+    /** */
+    private PluginContext ctx;
+
+    /** */
+    private IgniteLogger log;
+
+    /** */
+    private IgniteEx grid;
+
+    /** */
+    private Server srv;
+
+    /** */
+    private final OpenAPI api = new OpenAPI();
+
+    /** */
+    public void context(PluginContext ctx) {
+        this.ctx = ctx;
+        grid = (IgniteEx)ctx.grid();
+        log = ctx.log(OpenApiCommandsRegistryInvokerPlugin.class);
+    }
+
+    /** */
+    public void onIgniteStart() {
+        // TODO: make this configurable.
+        int port = 8080;
+        String host = "localhost";
+        String protocol = "http";
+
+        api.info(new Info()
+            .title("Ignite management API")
+            .description("This endpoint expose Apache Ignite management API 
methods")
+            .version(IgniteVersionUtils.VER_STR)
+        ).servers(singletonList(
+            new io.swagger.v3.oas.models.servers.Server()
+                .url(protocol + "://" + host + ":" + port + INVOKER_BASE_URL)
+                .description("Ignite node[id=" + grid.localNode().id() + ']')
+        ));
+
+        grid.commands().forEach(cmd -> register(cmd.getKey(), new 
LinkedList<>(), cmd.getValue()));
+
+        try {
+            SwaggerConfiguration cfg = new SwaggerConfiguration();
+
+            cfg.setId(API_ID);
+            cfg.setOpenAPI(api);
+            cfg.prettyPrint(true);
+
+            OpenApiContext ctx = new GenericOpenApiContextBuilder<>()
+                .ctxId(API_CTX_ID)
+                .openApiConfiguration(cfg)
+                .buildContext(true);
+
+            OpenApiContextLocator.getInstance().putOpenApiContext(API_CTX_ID, 
ctx);
+
+            srv = new Server();
+
+            ServerConnector connector = new ServerConnector(srv);
+
+            connector.setPort(port);
+            connector.setHost(host);
+
+            srv.addConnector(connector);
+
+            ServletContextHandler handler = new ServletContextHandler();
+
+            ServletHolder apiExposeServlet = new 
ServletHolder(OpenApiServlet.class);
+
+            apiExposeServlet.setInitParameter(OPENAPI_CONTEXT_ID_KEY, 
API_CTX_ID);
+
+            handler.addServlet(apiExposeServlet, "/api/*");
+            handler.addServlet(
+                new ServletHolder(new ManagementApiServlet(grid, 
INVOKER_BASE_URL)),
+                INVOKER_BASE_URL + "/*"
+            );
+            handler.addFilter(HeaderFilter.class, "/*", EnumSet.of(REQUEST));
+
+            srv.setHandler(handler);
+
+            srv.start();
+        }
+        catch (Exception e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** */
+    public <A extends IgniteDataTransferObject> void register(String name, 
List<String> parents, Command<A, ?, ?> cmd) {
+        if (cmd instanceof CommandsRegistry) {
+            parents.add(formattedName(name, CMD_WORDS_DELIM));
+
+            ((Iterable<Map.Entry<String, Command<?, ?, ?>>>)cmd).forEach(
+                cmd0 -> register(cmd0.getKey(), parents, cmd0.getValue())
+            );
+
+            parents.remove(parents.size() - 1);
+
+            if (!((CommandWithSubs)cmd).canBeExecuted())
+                return;
+        }
+
+        StringBuilder path = new StringBuilder();
+
+        for (String parent : parents)
+            path.append('/').append(parent);
+
+        path.append('/').append(formattedName(name, CMD_WORDS_DELIM));
+
+        List<Parameter> params = new ArrayList<>();
+
+        Consumer<Field> fldCnsmr = fld -> params.add(new Parameter()
+            .style(SIMPLE)
+            .in("query")
+            .name(formattedName(fld.getName(), CMD_WORDS_DELIM))
+            .schema(schema(fld.getType()))
+            .description(fld.getAnnotation(Argument.class).description())
+            .example(valueExample(fld))
+            .required(!fld.getAnnotation(Argument.class).optional()));
+
+        // TODO: support oneOf in spec.
+        visitCommandParams(
+            cmd.args(),
+            fld -> {
+                assert !fld.getAnnotation(PositionalArgument.class).optional();
+
+                path.append("/{").append(formattedName(fld.getName(), 
PARAM_WORDS_DELIM)).append('}');
+
+                params.add(new Parameter()
+                    .style(SIMPLE)
+                    .in("path")
+                    .name(formattedName(fld.getName(), PARAM_WORDS_DELIM))
+                    
.description(fld.getAnnotation(PositionalArgument.class).description())
+                    .example(valueExample(fld)));
+            },
+            fldCnsmr,
+            (optional, flds) -> flds.forEach(fldCnsmr)
+        );
+
+        Content plainText = new Content().addMediaType(TEXT_PLAIN, new 
MediaType());
+
+        api.path(
+            path.toString(),
+            new PathItem().get(new Operation()
+                .description(cmd.description())
+                .parameters(params)
+                .responses(new ApiResponses()
+                    .addApiResponse(Integer.toString(SC_OK), new ApiResponse()
+                        .description("Command output")
+                        .content(plainText))
+                    
.addApiResponse(Integer.toString(SC_INTERNAL_SERVER_ERROR), new ApiResponse()
+                        .description("Error text")
+                        .content(plainText)))
+        ));
+    }
+
+    private Schema<?> schema(Class<?> cls) {
+        if (cls == Float.class || cls == float.class)
+            return new NumberSchema().format("float");
+        else if (cls == Double.class || cls == double.class)
+            return new NumberSchema().format("double");
+        else if (cls == short.class
+            || cls == Short.class
+            || cls == byte.class
+            || cls == Byte.class)
+            return new IntegerSchema().format(null);
+        else if (cls == int.class || cls == Integer.class)
+            return new IntegerSchema();
+        else if (cls == long.class || cls == Long.class)
+            return new IntegerSchema().format("int64");
+        else if (cls == Boolean.class || cls == boolean.class)
+            return new BooleanSchema();
+        else if (cls == String.class)
+            return new StringSchema();
+        else if (cls == UUID.class)
+            return new StringSchema().format("uuid");
+        else if (cls == IgniteUuid.class)
+            return new StringSchema();
+        else if (cls.isArray())
+            return new ArraySchema().items(schema(cls.getComponentType()));
+
+        throw new IllegalArgumentException("Type not supported: " + cls);
+    }
+
+    /** */
+    public void onIgniteStop() {
+        try {
+            if (srv != null)
+                srv.stop();
+        }
+        catch (Exception e) {
+            log.warning("Error stop OpenApi server", e);
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPluginProvider.java
 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPluginProvider.java
new file mode 100644
index 00000000..f887dc94
--- /dev/null
+++ 
b/modules/openapi-management-ext/src/main/java/org/apache/ignite/internal/management/openapi/OpenApiCommandsRegistryInvokerPluginProvider.java
@@ -0,0 +1,109 @@
+/*
+ * 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.openapi;
+
+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;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/** */
+public class OpenApiCommandsRegistryInvokerPluginProvider implements 
PluginProvider {
+    /** */
+    private OpenApiCommandsRegistryInvokerPlugin invoker = new 
OpenApiCommandsRegistryInvokerPlugin();
+
+    /** {@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 invoker;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void initExtensions(PluginContext ctx, ExtensionRegistry 
registry) throws IgniteCheckedException {
+        invoker.context(ctx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CachePluginProvider 
createCacheProvider(CachePluginContext ctx) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onIgniteStart() throws IgniteCheckedException {
+        invoker.onIgniteStart();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onIgniteStop(boolean cancel) {
+        invoker.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/openapi-management-ext/src/test/java/org/apache/ignite/internal/management/openapi/OpenApiSelfTest.java
 
b/modules/openapi-management-ext/src/test/java/org/apache/ignite/internal/management/openapi/OpenApiSelfTest.java
new file mode 100644
index 00000000..3babb97c
--- /dev/null
+++ 
b/modules/openapi-management-ext/src/test/java/org/apache/ignite/internal/management/openapi/OpenApiSelfTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.openapi;
+
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class OpenApiSelfTest extends GridCommonAbstractTest {
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setPluginProviders(new 
OpenApiCommandsRegistryInvokerPluginProvider());
+    }
+
+    /** */
+    @Test
+    public void testExportOpenApiDefinition() throws Exception {
+        startGrid(0);
+
+        Thread.sleep(3 * 60_000);
+    }
+}
diff --git a/pom.xml b/pom.xml
index c1f8a1a5..37b5a88e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,7 @@
         <module>modules/cloud-ext</module>
         <module>modules/osgi-ext</module>
         <module>modules/ssh-ext</module>
+        <module>modules/openapi-management-ext</module>
     </modules>
 
     <profiles>

Reply via email to