blob: cd531099f3f7c5175792fcea900408a6fcc41008 [file] [log] [blame]
/*
* 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.
*/
#include <memory>
#include <unordered_map>
#include "celix/BundleActivator.h"
#include "celix/IShellCommand.h"
#include "std_commands.h"
#include "celix_shell_command.h"
#include "celix_log_service.h"
#include "celix_shell_command.h"
#include "celix_shell.h"
namespace celix {
class Shell {
public:
/**
* Try to execute a command using the provided command line.
*/
celix_status_t executeCommand(const char *commandLine, FILE *out, FILE *err) {
std::vector<std::string> tokens = tokenize(commandLine);
if (tokens.empty()) {
fprintf(err, "Invalid commandLine '%s'\n", commandLine);
return CELIX_ILLEGAL_ARGUMENT;
}
Entry entry = findCommand(tokens[0], err);
if (entry.cCommand) {
bool successful = entry.cCommand->executeCommand(entry.cCommand->handle, commandLine, out, err);
return successful ? CELIX_SUCCESS : CELIX_SERVICE_EXCEPTION;
} else if (entry.cxxCommand) {
std::vector<std::string> arguments{};
auto start = ++tokens.begin();
arguments.insert(arguments.begin(), start, tokens.end());
entry.cxxCommand->executeCommand(commandLine, arguments, out, err);
return CELIX_SUCCESS;
} else {
fprintf(err, "No command '%s'. Provided command line: %s\n", tokens[0].c_str(), commandLine);
return CELIX_ILLEGAL_ARGUMENT;
}
}
/**
* List the registered command names. Caller is owner of the commands.
* @return A celix array list with char*.
*/
celix_status_t getCommands(celix_array_list_t **commands) {
auto* result = celix_arrayList_create();
std::lock_guard<std::mutex> lck{mutex};
for (const auto& pair : entries) {
celix_arrayList_add(result, celix_utils_strdup(pair.first.c_str()));
}
*commands = result;
return CELIX_SUCCESS;
}
/**
* Gets the usage info for the provided command str. Caller is owner.
*/
celix_status_t getCommandUsage(const char *commandName, char **usageStr) {
std::lock_guard<std::mutex> lck{mutex};
auto it = entries.find(commandName);
if (it != entries.end()) {
std::string str = it->second.cCommand ?
it->second.properties->get(CELIX_SHELL_COMMAND_USAGE, "") :
it->second.properties->get(celix::IShellCommand::COMMAND_USAGE, "");
*usageStr = celix_utils_strdup(str.c_str());
} else {
*usageStr = nullptr;
}
return CELIX_SUCCESS;
}
/**
* Gets the usage info for the provided command str. Caller is owner.
*/
celix_status_t getCommandDescription(const char *commandName, char **commandDescription) {
std::lock_guard<std::mutex> lck{mutex};
auto it = entries.find(commandName);
if (it != entries.end()) {
std::string str = it->second.cCommand ?
it->second.properties->get(CELIX_SHELL_COMMAND_DESCRIPTION, "") :
it->second.properties->get(celix::IShellCommand::COMMAND_DESCRIPTION, "");
*commandDescription = celix_utils_strdup(str.c_str());
} else {
*commandDescription = nullptr;
}
return CELIX_SUCCESS;
}
void addCCommand(const std::shared_ptr<celix_shell_command>& command, const std::shared_ptr<const celix::Properties>& properties) {
addEntry(command, nullptr, properties);
}
void remCCommand(const std::shared_ptr<celix_shell_command>& command, const std::shared_ptr<const celix::Properties>& properties) {
remEntry(command, nullptr, properties);
}
void addCxxCommand(const std::shared_ptr<celix::IShellCommand>& command, const std::shared_ptr<const celix::Properties>& properties) {
addEntry(nullptr, command, properties);
}
void remCxxCommand(const std::shared_ptr<celix::IShellCommand>& command, const std::shared_ptr<const celix::Properties>& properties) {
remEntry(nullptr, command, properties);
}
void setLogService(const std::shared_ptr<celix_log_service> ls) {
std::lock_guard<std::mutex> lck{mutex};
logService = ls;
}
private:
struct Entry {
long svcId;
std::string name; //original name (fully qualified, e.g. "celix::lb")
std::string localName; //only the local part of a name (e.g. "lb")
std::shared_ptr<celix_shell_command> cCommand;
std::shared_ptr<celix::IShellCommand> cxxCommand;
std::shared_ptr<const celix::Properties> properties;
};
void addEntry(
const std::shared_ptr<celix_shell_command>& cCommand,
const std::shared_ptr<celix::IShellCommand>& cxxCommand,
const std::shared_ptr<const celix::Properties>& properties) {
std::lock_guard<std::mutex> lck{mutex};
std::string name = cCommand ?
properties->get(CELIX_SHELL_COMMAND_NAME, "") :
properties->get(celix::IShellCommand::COMMAND_NAME, "");
long svcId = properties->getAsLong(celix::SERVICE_ID, -1L);
auto it = entries.find(name);
if (it == entries.end()) {
auto* nsBegin = strstr(name.c_str(), "::");
std::string localName = nsBegin == nullptr ? name : std::string{nsBegin + 2};
entries.emplace(name, Entry{.svcId=svcId, .name=name, .localName=localName, .cCommand=cCommand, .cxxCommand=cxxCommand, .properties=properties});
} else if (logService){
logService->warning(logService->handle, "Cannot add command with name %s. A command with this name already exists", name.c_str());
}
}
void remEntry(
const std::shared_ptr<celix_shell_command>& cCommand,
const std::shared_ptr<celix::IShellCommand>& /*cxxCommand*/,
const std::shared_ptr<const celix::Properties>& properties) {
std::lock_guard<std::mutex> lck{mutex};
std::string name = cCommand ?
properties->get(CELIX_SHELL_COMMAND_NAME, "") :
properties->get(celix::IShellCommand::COMMAND_NAME, "");
long svcId = properties->getAsLong(celix::SERVICE_ID, -1L);
auto it = entries.find(name);
if (it != entries.end() && it->second.svcId == svcId) {
entries.erase(it);
}
}
std::vector<std::string> tokenize(const char* input) {
std::vector<std::string> result{};
char* str = celix_utils_strdup(input);
char* savePtr = nullptr;
char* token = strtok_r(str, " ", &savePtr);
while (token != nullptr) {
result.emplace_back(token);
token = strtok_r(nullptr, " ", &savePtr);
}
free(str);
return result;
}
Entry findCommand(const std::string& commandName, FILE * err) {
std::lock_guard<std::mutex> lck{mutex};
auto it = entries.find(commandName);
if (it != entries.end()) {
return it->second;
}
//not found, check if entry can be found without namespace
int nrFound = 0;
for (const auto& pair : entries) {
if (pair.second.localName == commandName) {
nrFound += 1;
}
}
if (nrFound == 1) {
for (const auto& pair : entries) {
if (pair.second.localName == commandName) {
return pair.second;
}
}
} else if (nrFound > 1) {
fprintf(err, "Found %i nr commands for command name %s. Please use a fully qualified name\n", nrFound, commandName.c_str());
}
return Entry{.svcId = -1, .name = "", .localName = "", .cCommand = {}, .cxxCommand = {}, .properties = {}};;
}
std::mutex mutex{}; //protects below
std::unordered_map<std::string, Entry> entries{};
std::shared_ptr<celix_log_service> logService{};
};
class ShellActivator {
public:
explicit ShellActivator(const std::shared_ptr <celix::BundleContext> &ctx) : commands{createCommands(ctx)} {
auto trk1 = ctx->trackServices<celix_shell_command>(CELIX_SHELL_COMMAND_SERVICE_NAME)
.addAddWithPropertiesCallback([this](const std::shared_ptr<celix_shell_command>& cmd, const std::shared_ptr<const celix::Properties>& properties){
shell.addCCommand(cmd, properties);
})
.addRemWithPropertiesCallback([this](const std::shared_ptr<celix_shell_command>& cmd, const std::shared_ptr<const celix::Properties>& properties){
shell.remCCommand(cmd, properties);
})
.build();
trackers.push_back(std::move(trk1));
auto trk2 = ctx->trackServices<celix::IShellCommand>()
.addAddWithPropertiesCallback([this](const std::shared_ptr<celix::IShellCommand>& cmd, const std::shared_ptr<const celix::Properties>& properties){
shell.addCxxCommand(cmd, properties);
})
.addRemWithPropertiesCallback([this](const std::shared_ptr<celix::IShellCommand>& cmd, const std::shared_ptr<const celix::Properties>& properties){
shell.remCxxCommand(cmd, properties);
})
.build();
trackers.push_back(std::move(trk2));
auto trk3 = ctx->trackServices<celix_log_service>()
.addSetCallback([this](const std::shared_ptr<celix_log_service>& logService){
shell.setLogService(logService);
})
.build();
trackers.push_back(std::move(trk3));
shellSvc.handle = this;
shellSvc.executeCommand = [](void *handle, const char *commandLine, FILE *out, FILE *err) -> celix_status_t {
auto* act = static_cast<ShellActivator*>(handle);
return act->shell.executeCommand(commandLine, out, err);
};
shellSvc.getCommands = [](void *handle, celix_array_list_t **commands) -> celix_status_t {
auto* act = static_cast<ShellActivator*>(handle);
return act->shell.getCommands(commands);
};
shellSvc.getCommandUsage = [](void *handle, const char *commandName, char **UsageStr) -> celix_status_t {
auto* act = static_cast<ShellActivator*>(handle);
return act->shell.getCommandUsage(commandName, UsageStr);
};
shellSvc.getCommandDescription = [](void *handle, const char *commandName, char **commandDescription) -> celix_status_t {
auto* act = static_cast<ShellActivator*>(handle);
return act->shell.getCommandDescription(commandName, commandDescription);
};
shellSvcReg = ctx->registerUnmanagedService<celix_shell>(&shellSvc, CELIX_SHELL_SERVICE_NAME)
.build();
}
private:
std::shared_ptr <celix_std_commands> createCommands(const std::shared_ptr <celix::BundleContext> &ctx) {
return std::shared_ptr<celix_std_commands>
{celix_stdCommands_create(ctx->getCBundleContext()), [](celix_std_commands *cmds) {
celix_stdCommands_destroy(cmds);
}};
}
Shell shell{};
celix_shell shellSvc{};
std::vector<std::shared_ptr<celix::GenericServiceTracker>> trackers{};
std::shared_ptr<celix::ServiceRegistration> shellSvcReg{};
std::shared_ptr<celix_std_commands> commands;
};
}
CELIX_GEN_CXX_BUNDLE_ACTIVATOR(celix::ShellActivator)