diff --git a/native/c/commands/server.cpp b/native/c/commands/server.cpp index d3de48a63..bef82546a 100644 --- a/native/c/commands/server.cpp +++ b/native/c/commands/server.cpp @@ -30,6 +30,7 @@ #include "../server/dispatcher.hpp" #include "../server/logger.hpp" #include "../server/worker.hpp" +#include "../server/plugin_bridge.hpp" using namespace parser; @@ -162,6 +163,7 @@ void ZServer::run(const server::Options &opts) LOG_DEBUG("Registering command handlers"); register_all_commands(dispatcher); + plugin::register_commands_with_server(dispatcher); worker_pool.reset(new WorkerPool(options.num_workers, std::chrono::seconds(options.request_timeout))); diff --git a/native/c/examples/sample-plugin/sample_plugin.cpp b/native/c/examples/sample-plugin/sample_plugin.cpp index fb7212c74..bc11b2b28 100644 --- a/native/c/examples/sample-plugin/sample_plugin.cpp +++ b/native/c/examples/sample-plugin/sample_plugin.cpp @@ -24,6 +24,9 @@ int hello_command(plugin::InvocationContext &context) { context.println(str.c_str()); } + auto result = ast::obj(); + result->set("message", ast::str(str)); + context.set_object(result); return 0; } @@ -39,6 +42,7 @@ void BasicCommandRegistry::register_commands(CommandProviderImpl::CommandRegistr ctx.set_handler(hello, hello_command); ctx.add_subcommand(sample_group, hello); ctx.add_subcommand(root, sample_group); + ctx.add_to_server(sample_group); } void register_plugin(plugin::PluginManager &pm) diff --git a/native/c/extend/plugin.cpp b/native/c/extend/plugin.cpp index 10342821f..ce5fb28f4 100644 --- a/native/c/extend/plugin.cpp +++ b/native/c/extend/plugin.cpp @@ -47,7 +47,7 @@ class RegistrationContextImpl void add_alias(CommandHandle command, const char *alias) { CommandRecord *record = to_record(command); - if (!record || !alias) + if (record == nullptr || alias == nullptr) return; record->get().add_alias(alias); @@ -63,7 +63,7 @@ class RegistrationContextImpl const DefaultValue *default_value) { CommandRecord *record = to_record(command); - if (!record || !name) + if (record == nullptr || name == nullptr) return; std::vector alias_vector; @@ -88,7 +88,7 @@ class RegistrationContextImpl const DefaultValue *default_value) { CommandRecord *record = to_record(command); - if (!record || !name) + if (record == nullptr || name == nullptr) return; parser::ArgValue default_arg = convert_default(default_value); @@ -102,7 +102,7 @@ class RegistrationContextImpl void set_handler(CommandHandle command, CommandHandler handler) { CommandRecord *record = to_record(command); - if (!record) + if (record == nullptr) return; record->get().set_handler(handler); @@ -112,7 +112,7 @@ class RegistrationContextImpl { CommandRecord *parent_record = to_record(parent); CommandRecord *child_record = to_record(child); - if (!parent_record || !child_record) + if (parent_record == nullptr || child_record == nullptr) return; if (parent_record->is_root()) @@ -126,6 +126,20 @@ class RegistrationContextImpl } } + void add_to_server(CommandHandle command) + { + CommandRecord *record = to_record(command); + if (record == nullptr) + return; + + m_server_commands.insert(record->get_command_ptr()); + } + + const std::set &get_server_commands() const + { + return m_server_commands; + } + private: struct CommandRecord { @@ -166,7 +180,7 @@ class RegistrationContextImpl parser::ArgValue convert_default(const DefaultValue *value) { - if (!value || value->kind == DefaultValue::ValueKind_None) + if (value == nullptr || value->kind == DefaultValue::ValueKind_None) { return parser::ArgValue(); } @@ -205,6 +219,7 @@ class RegistrationContextImpl parser::Command &m_root; CommandRecord m_root_record; std::vector> m_records; + std::set m_server_commands; }; void PluginManager::load_plugins() @@ -218,7 +233,7 @@ void PluginManager::load_plugins() { std::string plugin_path = std::string("./plugins/") + entry->d_name; void *plugin = dlopen(plugin_path.c_str(), RTLD_LAZY); - if (!plugin) + if (plugin == nullptr) { ZLOG_ERROR("Failed to open handle to plugin located at: %s", plugin_path.c_str()); continue; @@ -259,6 +274,8 @@ void PluginManager::load_plugins() void PluginManager::unload_plugins() { + m_command_providers.clear(); + m_server_commands.clear(); for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it) { if (it->handle) @@ -304,5 +321,10 @@ void PluginManager::register_commands(parser::Command &rootCommand) provider->register_commands(context); } + + for (const auto &command : context.get_server_commands()) + { + m_server_commands.insert(command); + } } } // namespace plugin diff --git a/native/c/extend/plugin.hpp b/native/c/extend/plugin.hpp index ce647f3c5..84a67ec33 100644 --- a/native/c/extend/plugin.hpp +++ b/native/c/extend/plugin.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -429,6 +430,7 @@ inline Node nil() namespace parser { class Command; +typedef std::shared_ptr command_ptr; } // namespace parser namespace plugin @@ -826,36 +828,30 @@ class Io void print(const char *s) const { - if (m_output_stream && s) + if (s) { - m_output_stream->write(s, std::strlen(s)); + output_stream().write(s, std::strlen(s)); } } void println(const char *s) const { print(s); - if (m_output_stream) - { - m_output_stream->put('\n'); - } + output_stream().put('\n'); } void err(const char *e) const { - if (m_error_stream && e) + if (e) { - m_error_stream->write(e, std::strlen(e)); + error_stream().write(e, std::strlen(e)); } } void errln(const char *e) const { err(e); - if (m_error_stream) - { - m_error_stream->put('\n'); - } + error_stream().put('\n'); } void to_err(const std::stringstream &sstr) @@ -1133,6 +1129,7 @@ class CommandProviderImpl const CommandDefaultValue *defaultValue) = 0; virtual void set_handler(CommandHandle command, CommandHandler handler) = 0; virtual void add_subcommand(CommandHandle parent, CommandHandle child) = 0; + virtual void add_to_server(CommandHandle command) = 0; }; virtual ~CommandProviderImpl() @@ -1200,6 +1197,11 @@ class PluginManager return m_plugins; } + const std::set &get_server_commands() const + { + return m_server_commands; + } + PluginManager(const PluginManager &) = delete; PluginManager &operator=(const PluginManager &) = delete; @@ -1210,6 +1212,7 @@ class PluginManager void discard_command_providers_from(std::size_t start_index); std::vector> m_command_providers; + std::set m_server_commands; std::vector m_plugins; PluginMetadata m_pending_metadata; bool m_metadata_pending; @@ -1268,9 +1271,8 @@ inline bool PluginManager::is_display_name_in_use(const std::string &name) const return false; } - return std::any_of(m_plugins.begin(), m_plugins.end(), [&name](const auto &plugin) { - return plugin.metadata.display_name == name; - }); + return std::any_of(m_plugins.begin(), m_plugins.end(), [&name](const auto &plugin) + { return plugin.metadata.display_name == name; }); } inline void PluginManager::discard_command_providers_from(std::size_t start_index) diff --git a/native/c/makefile b/native/c/makefile index 1b5ea2811..d41ac129d 100644 --- a/native/c/makefile +++ b/native/c/makefile @@ -73,6 +73,7 @@ COMMAND_OBJS = $(OUT_DIR)/commands/console.o \ SERVER_OBJS = $(OUT_DIR)/server/builder.o \ $(OUT_DIR)/server/rpc_commands.o \ $(OUT_DIR)/server/dispatcher.o \ + $(OUT_DIR)/server/plugin_bridge.o \ $(OUT_DIR)/server/rpcio.o \ $(OUT_DIR)/server/rpc_server.o \ $(OUT_DIR)/server/validator.o \ diff --git a/native/c/server/plugin_bridge.cpp b/native/c/server/plugin_bridge.cpp new file mode 100644 index 000000000..d19c294d5 --- /dev/null +++ b/native/c/server/plugin_bridge.cpp @@ -0,0 +1,163 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +#include "plugin_bridge.hpp" +#include "dispatcher.hpp" +#include "builder.hpp" +#include "logger.hpp" +#include +#include +#include +#include "../commands/core.hpp" + +namespace plugin +{ + +// Forward declaration for recursion +static void traverse_and_register_impl(parser::Command *cmd, std::string &path_prefix, CommandDispatcher &dispatcher, int depth); + +/** + * @brief Recursively traverse command tree and register to middleware + * + * @param cmd The command to process + * @param path_prefix The command path built so far (e.g., "sample.hello") + * @param dispatcher The dispatcher to register commands to + */ +static void traverse_and_register(parser::Command *cmd, const std::string &path_prefix, CommandDispatcher &dispatcher) +{ + // Limit recursion depth to prevent stack issues on z/OS + std::string mutable_path = path_prefix; + traverse_and_register_impl(cmd, mutable_path, dispatcher, 0); +} + +static void traverse_and_register_impl(parser::Command *cmd, std::string &path_prefix, CommandDispatcher &dispatcher, int depth) +{ + if (!cmd || depth > 10) // Prevent deep recursion on z/OS + return; + + // Get handler for this command + typedef plugin::CommandProviderImpl::CommandRegistrationContext::CommandHandler CommandHandler; + CommandHandler handler = cmd->get_handler(); + + // Check if this command has subcommands + const std::map &subcommands = cmd->get_commands(); + + if (handler != nullptr) + { + // This command has a handler, register it with dot notation + std::string rpc_command_name = path_prefix; + std::replace(rpc_command_name.begin(), rpc_command_name.end(), ' ', '.'); + + try + { + LOG_DEBUG("Registering plugin command: %s (from path: %s)", rpc_command_name.c_str(), path_prefix.c_str()); + + CommandBuilder builder(handler); + + // For plugin commands, arguments from JSON are passed in camelCase by the middleware. + // Plugins typically register CLI-style argument names (kebab-case) using add_keyword_arg. + // We dynamically infer renames from the command's declared arguments. + for (const auto &arg : cmd->get_args()) + { + // Help flags and automatically generated 'no-' flags are CLI-specific + // and shouldn't be exposed as separate JSON-RPC parameters. + if (arg.is_help_flag || arg.name.rfind("no-", 0) == 0) + continue; + + // If the arg name has hyphens, map its camelCase equivalent + // to the kebab-case name expected by the handler. + if (arg.name.find('-') != std::string::npos) + { + std::string camel_name; + bool capitalize_next = false; + for (char c : arg.name) + { + if (c == '-') + { + capitalize_next = true; + } + else + { + camel_name += capitalize_next ? static_cast(std::toupper(c)) : c; + capitalize_next = false; + } + } + builder.rename_arg(camel_name, arg.name); + } + + // Also apply default values if provided + if (!arg.default_value.is_none()) + { + if (arg.default_value.is_bool()) + builder.set_default(arg.name, *arg.default_value.get_bool()); + else if (arg.default_value.is_int()) + builder.set_default(arg.name, *arg.default_value.get_int()); + else if (arg.default_value.is_double()) + builder.set_default(arg.name, *arg.default_value.get_double()); + else if (arg.default_value.is_string()) + builder.set_default(arg.name, *arg.default_value.get_string()); + } + } + + bool success = dispatcher.register_command(rpc_command_name, builder); + + if (!success) + { + LOG_ERROR("Failed to register plugin command: %s", rpc_command_name.c_str()); + } + } + catch (const std::exception &e) + { + LOG_ERROR("Exception registering plugin command %s: %s", rpc_command_name.c_str(), e.what()); + } + catch (...) + { + LOG_ERROR("Unknown exception registering plugin command: %s", rpc_command_name.c_str()); + } + } + + // Recursively process subcommands + // Build subcommand path and recurse - avoid creating many temporary strings + size_t original_length = path_prefix.length(); + for (std::map::const_iterator it = subcommands.begin(); + it != subcommands.end(); ++it) + { + if (it->second) // Null check for safety + { + // Append to existing string instead of creating new ones + path_prefix += "."; + path_prefix += it->first; + + traverse_and_register_impl(it->second.get(), path_prefix, dispatcher, depth + 1); + + // Restore original path for next sibling + path_prefix.resize(original_length); + } + } +} + +void register_commands_with_server(CommandDispatcher &dispatcher) +{ + const auto &server_commands = core::get_plugin_manager()->get_server_commands(); + if (!server_commands.empty()) + { + LOG_DEBUG("Registering plugin commands to middleware"); + } + + // Traverse top-level commands and register them to middleware + for (const auto &command : server_commands) + { + LOG_DEBUG("Registering top-level plugin command: %s", command->get_name().c_str()); + traverse_and_register(command.get(), command->get_name(), dispatcher); + } +} + +} // namespace plugin diff --git a/native/c/server/plugin_bridge.hpp b/native/c/server/plugin_bridge.hpp new file mode 100644 index 000000000..306d1e14d --- /dev/null +++ b/native/c/server/plugin_bridge.hpp @@ -0,0 +1,33 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +#ifndef PLUGIN_BRIDGE_HPP +#define PLUGIN_BRIDGE_HPP + +#include "../extend/plugin.hpp" +#include "../parser.hpp" + +// Forward declarations +class CommandDispatcher; + +namespace plugin +{ + +/** + * @brief Register all plugin commands to the middleware dispatcher + * + * @param dispatcher The CommandDispatcher to register commands to + */ +void register_commands_with_server(CommandDispatcher &dispatcher); + +} // namespace plugin + +#endif diff --git a/native/c/server/rpc_commands.cpp b/native/c/server/rpc_commands.cpp index 5a8aad00d..4a412c538 100644 --- a/native/c/server/rpc_commands.cpp +++ b/native/c/server/rpc_commands.cpp @@ -159,7 +159,6 @@ void register_uss_commands(CommandDispatcher &dispatcher) dispatcher.register_command("deleteFile", create_uss_builder(uss::handle_uss_delete) .validate()); - // {"id": 1, "jsonrpc": "2.0", "method": "moveFile", "params": {"source": "source-path", "target": "target-path", "force": false}} dispatcher.register_command("moveFile", create_uss_builder(uss::handle_uss_move) .validate()