This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/cxx in repository https://gitbox.apache.org/repos/asf/celix.git
commit 9231430205ed008ae79342e25b49d1178e793e67 Author: Pepijn Noltes <[email protected]> AuthorDate: Wed Jan 2 14:53:06 2019 +0100 CELIX-438: Adds the C++ shell/shell_tui initial impl and a small test application --- bundles/shell/CMakeLists.txt | 1 + bundles/shell/cxx_shell/CMakeLists.txt | 18 +- bundles/shell/cxx_shell/include/celix/IShell.h | 2 +- .../shell/cxx_shell/include/celix/IShellCommand.h | 18 +- bundles/shell/cxx_shell/src/HelpCommand.cc | 86 +++++++++ bundles/shell/cxx_shell/src/InspectCommand.cc | 70 +++++++ bundles/shell/cxx_shell/src/LbCommand.cc | 54 ++++++ bundles/shell/cxx_shell/src/ShellActivator.cc | 157 ++++++--------- bundles/shell/cxx_shell/src/StopAndStartCommand.cc | 75 ++++++++ .../shell/cxx_shell/src/commands.h | 27 ++- .../{cxx_shell => cxx_shell_tui}/CMakeLists.txt | 29 +-- .../shell/{ => cxx_shell_tui/gtest}/CMakeLists.txt | 16 +- .../IShell.h => cxx_shell_tui/gtest/src/main.cc} | 23 +-- .../shell/cxx_shell_tui/src/ShellTuiActivator.cc | 150 +++++++++++++++ .../IShell.h => cxx_shell_tui/src/shell_test.cc} | 23 ++- cmake/celix_project/AddGTest.cmake | 4 +- libs/framework_cxx/gtest/src/Framework_tests.cc | 81 ++++---- libs/framework_cxx/include/celix/Framework.h | 45 +++-- libs/framework_cxx/include/celix/IBundle.h | 1 - .../framework_cxx/include/celix/IBundleActivator.h | 12 +- libs/framework_cxx/include/celix/IBundleContext.h | 7 +- libs/framework_cxx/src/BundleImpl.h | 67 +++---- libs/framework_cxx/src/Framework.cc | 114 +++++++---- .../gtest/src/RegistryConcurrency_tests.cc | 3 +- libs/registry/gtest/src/Registry_tests.cc | 23 ++- libs/registry/gtest/src/ServiceTracking_tests.cc | 64 +++---- libs/registry/include/celix/Constants.h | 7 +- libs/registry/include/celix/Properties.h | 6 + libs/registry/include/celix/ServiceRegistry.h | 155 ++++++++------- libs/registry/src/ServiceRegistry.cc | 211 ++++++++++++++------- 30 files changed, 1048 insertions(+), 501 deletions(-) diff --git a/bundles/shell/CMakeLists.txt b/bundles/shell/CMakeLists.txt index 2fd81dc..851d601 100644 --- a/bundles/shell/CMakeLists.txt +++ b/bundles/shell/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(shell_bonjour) add_subdirectory(shell_tui) add_subdirectory(cxx_shell) +add_subdirectory(cxx_shell_tui) diff --git a/bundles/shell/cxx_shell/CMakeLists.txt b/bundles/shell/cxx_shell/CMakeLists.txt index 070f05e..5373caf 100644 --- a/bundles/shell/cxx_shell/CMakeLists.txt +++ b/bundles/shell/cxx_shell/CMakeLists.txt @@ -19,18 +19,22 @@ find_package(glog REQUIRED) #TODO rename to celix::shell && celix::shell_api -add_library(shell_api_cxx INTERFACE) -target_include_directories(shell_api INTERFACE +add_library(celix_cxx_shell_api INTERFACE) +target_include_directories(celix_cxx_shell_api INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include> $<INSTALL_INTERFACE:include/celix/shell> ) -add_library(celix_shell_cxx STATIC +add_library(celix_cxx_shell SHARED src/ShellActivator.cc - include/celix/IShell.h) -target_include_directories(celix_shell_cxx PRIVATE src) -target_include_directories(celix_shell_cxx PUBLIC include) -target_link_libraries(celix_shell_cxx PRIVATE glog::glog celix_framework_cxx) + src/LbCommand.cc + src/HelpCommand.cc + src/StopAndStartCommand.cc + src/InspectCommand.cc +) +target_include_directories(celix_cxx_shell PRIVATE src) +target_link_libraries(celix_cxx_shell PRIVATE celix_cxx_shell_api) +target_link_libraries(celix_cxx_shell PUBLIC glog::glog celix_framework_cxx) #if (ENABLE_TESTING) # add_subdirectory(gtest) diff --git a/bundles/shell/cxx_shell/include/celix/IShell.h b/bundles/shell/cxx_shell/include/celix/IShell.h index 9809ac5..f272c52 100644 --- a/bundles/shell/cxx_shell/include/celix/IShell.h +++ b/bundles/shell/cxx_shell/include/celix/IShell.h @@ -29,7 +29,7 @@ namespace celix { virtual ~IShell() = default; - virtual bool executeCommand(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept = 0; + virtual bool executeCommandLine(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept = 0; }; } diff --git a/bundles/shell/cxx_shell/include/celix/IShellCommand.h b/bundles/shell/cxx_shell/include/celix/IShellCommand.h index 8a30133..ec98c73 100644 --- a/bundles/shell/cxx_shell/include/celix/IShellCommand.h +++ b/bundles/shell/cxx_shell/include/celix/IShellCommand.h @@ -18,6 +18,7 @@ */ #include <string> +#include <vector> #include <iostream> #ifndef CXX_CELIX_ISHELLCOMMAND_H @@ -29,20 +30,21 @@ namespace celix { public: static constexpr const char * const SERVICE_FQN = "celix::IShellCommand [Version 1]"; - static constexpr const char * const COMMAND_NAME = "name"; - static constexpr const char * const COMMAND_USAGE = "usage"; - static constexpr const char * const COMMAND_DESCRIPTION = "description"; + static constexpr const char * const COMMAND_NAME = "COMMAND_NAME"; + static constexpr const char * const COMMAND_USAGE = "COMMAND_USAGE"; + static constexpr const char * const COMMAND_DESCRIPTION = "COMMAND_DESCRIPTION"; virtual ~IShellCommand() = default; - virtual void executeCommand(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept = 0; + virtual void executeCommand(const std::string &cmdName, const std::vector<std::string> &cmdArgs, std::ostream &out, std::ostream &err) noexcept = 0; }; - using ShellCommandFunction = std::function<void(const std::string &commandLine, std::ostream &out, std::ostream &err)>; static constexpr const char * const SHELL_COMMAND_FUNCTION_SERVICE_FQN = "celix::ShellFunction [Version 1]"; - static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_NAME = "name"; - static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_USAGE = "usage"; - static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION = "description"; + static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_NAME = "COMMAND_NAME"; + static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_USAGE = "COMMAND_USAGE"; + static constexpr const char * const SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION = "COMMAND_DESCRIPTION"; + using ShellCommandFunction = std::function<void(const std::string &cmdName, const std::vector<std::string> &cmdArgs, std::ostream &out, std::ostream &err)>; + } #endif //CXX_CELIX_ISHELLCOMMAND_H diff --git a/bundles/shell/cxx_shell/src/HelpCommand.cc b/bundles/shell/cxx_shell/src/HelpCommand.cc new file mode 100644 index 0000000..3f5f5e9 --- /dev/null +++ b/bundles/shell/cxx_shell/src/HelpCommand.cc @@ -0,0 +1,86 @@ +/** + *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 "commands.h" + +#include "celix/api.h" +#include "celix/IShellCommand.h" + +celix::ServiceRegistration impl::registerHelp(std::shared_ptr<celix::IBundleContext> ctx) { + + celix::ShellCommandFunction help = [ctx](const std::string &, const std::vector<std::string> &commandArguments, std::ostream &out, std::ostream &) { + + if (commandArguments.empty()) { //only command -> overview + + std::string hasCommandNameFilter = std::string{"("} + celix::IShellCommand::COMMAND_NAME + "=*)"; + std::vector<std::string> commands{}; + ctx->useServices<celix::IShellCommand>([&](celix::IShellCommand &, const celix::Properties &props) { + commands.push_back(celix::getProperty(props, celix::IShellCommand::COMMAND_NAME, "!Error!")); + }, hasCommandNameFilter); + + hasCommandNameFilter = std::string{"("} + celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME + "=*)"; + + std::function<void(celix::ShellCommandFunction &, const celix::Properties &)> use = [&]( + celix::ShellCommandFunction &, const celix::Properties &props) { + commands.push_back(celix::getProperty(props, celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME, "!Error!")); + }; + ctx->useFunctionServices(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, use, hasCommandNameFilter); + + //TODO useCService with a shell command service struct + + out << "Available commands: " << std::endl; + for (auto &name : commands) { + out << "|- " << name << std::endl; + } + } else { //details + for (auto &cmd : commandArguments) { + std::string commandNameFilter = std::string{"("} + celix::IShellCommand::COMMAND_NAME + "=" + cmd + ")"; + bool found = ctx->useService<celix::IShellCommand>([&](celix::IShellCommand &, const celix::Properties &props) { + out << "Command Name : " << celix::getProperty(props, celix::IShellCommand::COMMAND_NAME, "!Error!") << std::endl; + out << "Command Usage : " << celix::getProperty(props, celix::IShellCommand::COMMAND_USAGE, "!Error!") << std::endl; + out << "Command Description: " << celix::getProperty(props, celix::IShellCommand::COMMAND_DESCRIPTION, "!Error!") << std::endl; + + }, commandNameFilter); + if (!found) { + commandNameFilter = std::string{"("} + celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME + "=" + cmd + ")"; + std::function<void(celix::ShellCommandFunction &, const celix::Properties &)> use = [&]( + celix::ShellCommandFunction &, const celix::Properties &props) { + out << "Command Name : " << celix::getProperty(props, celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME, "!Error!") << std::endl; + out << "Command Usage : " << celix::getProperty(props, celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE, "!Error!") << std::endl; + out << "Command Description: " << celix::getProperty(props, celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION, "!Error!") << std::endl; + }; + found = ctx->useFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, use, commandNameFilter); + } + if (!found) { + //TODO use C cmd service + } + if (!found) { + out << "Command '" << cmd << "' not available" << std::endl; + } + out << std::endl; + } + } + }; + + celix::Properties props{}; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME] = "help"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE] = "help [command name]"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION] = "display available commands and description."; + return ctx->registerFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, std::move(help), std::move(props)); +} \ No newline at end of file diff --git a/bundles/shell/cxx_shell/src/InspectCommand.cc b/bundles/shell/cxx_shell/src/InspectCommand.cc new file mode 100644 index 0000000..da3bf24 --- /dev/null +++ b/bundles/shell/cxx_shell/src/InspectCommand.cc @@ -0,0 +1,70 @@ +/** + *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 "commands.h" + +#include <functional> + +#include "celix/api.h" +#include "celix/IShellCommand.h" + +namespace { + + void inspect(std::shared_ptr<celix::IBundleContext> ctx, const std::string &, const std::vector<std::string> &cmdArgs, std::ostream &out, std::ostream &) { + if (cmdArgs.empty()) { + out << "Provide a bundle id" << std::endl; + } else { + auto &bndId = cmdArgs[0]; + auto servicesNames = ctx->registry().listAllRegisteredServiceNames(); + + const std::string *what{nullptr}; + if (cmdArgs.size() >= 2) { + what = &cmdArgs[1]; + } + + if (what == nullptr || *what == "provided") { + out << "Provided Services: \n"; + for (auto &svcName : servicesNames) { + std::string filter = std::string{"("} + celix::SERVICE_BUNDLE + "=" + bndId + ")"; + ctx->registry().useAnyServices(svcName, [&out](std::shared_ptr<void>, const celix::Properties &props, const celix::IResourceBundle &) { + out << "|- Service " << celix::getProperty(props, celix::SERVICE_ID, "!Error") << ":\n"; + for (auto &pair : props) { + out << " |- " << pair.first << " = " << pair.second << std::endl; + } + }, filter, ctx->bundle()); + } + } + if (what == nullptr || *what == "tracked") { + //TODO trackers + } + } + } +} + + +celix::ServiceRegistration impl::registerInspect(std::shared_ptr<celix::IBundleContext> ctx) { + using namespace std::placeholders; + celix::ShellCommandFunction cmd = std::bind(&inspect, ctx, _1, _2, _3, _4); + + celix::Properties props{}; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME] = "inspect"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE] = "inspect bndId [provided|tracked]"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION] = "Inspects a bundle. Showing the provided and/or tracked services"; + return ctx->registerFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, std::move(cmd), std::move(props)); +} \ No newline at end of file diff --git a/bundles/shell/cxx_shell/src/LbCommand.cc b/bundles/shell/cxx_shell/src/LbCommand.cc new file mode 100644 index 0000000..5c1b07c --- /dev/null +++ b/bundles/shell/cxx_shell/src/LbCommand.cc @@ -0,0 +1,54 @@ +/** + *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 "commands.h" + +#include "celix/IShellCommand.h" + +namespace { + class LbCommand : public celix::IShellCommand { + public: + LbCommand(std::shared_ptr<celix::IBundleContext> _ctx) : ctx{std::move(_ctx)} {} + + void executeCommand(const std::string &, const std::vector<std::string> &cmdArgs, std::ostream &out, + std::ostream &) noexcept override { + if (cmdArgs.empty()) { + out << "Bundles: " << std::endl; + ctx->useBundles([&out](const celix::IBundle &bnd) { + //TODO make aligned text table + out << "|- " << bnd.id() << ": " << bnd.name() << std::endl; + }, true); + } + //TODO parse args + } + + private: + std::shared_ptr<celix::IBundleContext> ctx; + }; +} + +celix::ServiceRegistration impl::registerLb(std::shared_ptr<celix::IBundleContext> ctx) { + celix::Properties props{}; + props[celix::IShellCommand::COMMAND_NAME] = "lb"; + props[celix::IShellCommand::COMMAND_USAGE] = "list bundles. Default only the groupless bundles are listed. Use -a to list all bundles." \ + "\nIf a group string is provided only bundles matching the group string will be listed." \ + "\nUse -l to print the bundle locations.\nUse -s to print the bundle symbolic names\nUse -u to print the bundle update location."; + props[celix::IShellCommand::COMMAND_DESCRIPTION] = "lb [-l | -s | -u | -a] [group]"; + return ctx->registerService(std::shared_ptr<celix::IShellCommand>{new LbCommand{ctx}}, std::move(props)); +} \ No newline at end of file diff --git a/bundles/shell/cxx_shell/src/ShellActivator.cc b/bundles/shell/cxx_shell/src/ShellActivator.cc index 5fd4f1f..ee89d98 100644 --- a/bundles/shell/cxx_shell/src/ShellActivator.cc +++ b/bundles/shell/cxx_shell/src/ShellActivator.cc @@ -21,125 +21,82 @@ #include "celix/IShellCommand.h" #include "celix/IShell.h" +#include "commands.h" + namespace { - class LbCommand : public celix::IShellCommand { + class Shell : public celix::IShell { public: - LbCommand(std::shared_ptr<celix::IBundleContext> _ctx) : ctx{std::move(_ctx)} {} - void executeCommand(const std::string &/*command line*/, std::ostream &out, std::ostream &) noexcept override { - //TODO parse commandLine - out << "Bundles: " << std::endl; - ctx->useBundles([&out](const celix::IBundle &bnd) { - out << "|- " << bnd.id() << ": " << bnd.name() << std::endl; - }, true); - } - private: - std::shared_ptr<celix::IBundleContext> ctx; - }; - - celix::ServiceRegistration registerLb(std::shared_ptr<celix::IBundleContext> ctx) { - celix::Properties props{}; - props[celix::IShellCommand::COMMAND_NAME] = "lb"; - props[celix::IShellCommand::COMMAND_USAGE] = "list installed bundles"; - props[celix::IShellCommand::COMMAND_DESCRIPTION] = "TODO"; - return ctx->registerService(std::shared_ptr<celix::IShellCommand>{new LbCommand{ctx}}, std::move(props)); - } - - celix::ServiceRegistration registerHelp(std::shared_ptr<celix::IBundleContext> ctx) { - - celix::ShellCommandFunction help = [ctx](const std::string &, std::ostream &out, std::ostream &) { - - std::string hasCommandNameFilter = std::string{"("} + celix::IShellCommand::COMMAND_NAME + "=*)"; - //TODO parse command line to see if details is requested instead of overview - std::vector<std::string> commands{}; - ctx->useServices<celix::IShellCommand>([&](celix::IShellCommand&, const celix::Properties &props) { - commands.push_back(celix::getProperty(props, celix::IShellCommand::COMMAND_NAME, "!Error!")); - }, hasCommandNameFilter); - - hasCommandNameFilter = std::string{"("} + celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME + "=*)"; - - std::function<void(celix::ShellCommandFunction&, const celix::Properties&)> use = [&](celix::ShellCommandFunction&, const celix::Properties &props) { - commands.push_back(celix::getProperty(props, celix::IShellCommand::COMMAND_NAME, "!Error!")); - }; - ctx->useFunctionServices(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, use, hasCommandNameFilter); + Shell(std::shared_ptr<celix::IBundleContext> _ctx) : ctx{std::move(_ctx)} {} + + bool executeCommandLine(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept override { + std::string cmdName{}; + std::vector<std::string> cmdArgs{}; + + char *savePtr = nullptr; + char *cl = strndup(commandLine.c_str(), 1024*1024); + char *token = strtok_r(cl, " ", &savePtr); + while (token != nullptr) { + if (cmdName.empty()) { + cmdName = std::string{token}; + } else { + cmdArgs.emplace_back(std::string{token}); + } + token = strtok_r(nullptr, " ", &savePtr); + } - //TODO useCService with a shell command service struct + bool commandCalled = false; - out << "Available commands: " << std::endl; - for (auto &name : commands) { - out << "|- " << name << std::endl; + if (!cmdName.empty()) { + std::string filter = + std::string{"("} + celix::IShellCommand::COMMAND_NAME + "=" + cmdName + ")"; + commandCalled = ctx->useService<celix::IShellCommand>([&](celix::IShellCommand &cmd) { + cmd.executeCommand(cmdName, cmdArgs, out, err); + }, filter); + } + if (!cmdName.empty() && !commandCalled) { + std::string filter = + std::string{"("} + celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME + "=" + cmdName + ")"; + std::function<void(celix::ShellCommandFunction&)> use = [&](celix::ShellCommandFunction &cmd) -> void { + cmd(cmdName, cmdArgs, out, err); + }; + commandCalled = ctx->useFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, use, filter); } - }; - - celix::Properties props{}; - props[celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME] = "help"; - props[celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE] = "help [command name]"; - props[celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION] = "TODO"; - return ctx->registerFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, std::move(help), std::move(props)); - } - class Shell : public celix::IShell { - public: - Shell(std::shared_ptr<celix::IBundleContext> _ctx) : ctx{std::move(_ctx)} { - celix::ServiceTrackerOptions<celix::IShellCommand> opts1{}; - opts1.updateWithProperties = [this](std::vector<std::tuple<celix::IShellCommand*,const celix::Properties*>> services) { - std::lock_guard<std::mutex> lck(commands.mutex); - commands.commands = std::move(services); - }; - trk1 = ctx->trackServices(opts1); + //TODO C command service struct + if (!cmdName.empty() && !commandCalled) { + out << "Command '" << cmdName << "' not available. Type 'help' to see a list of available commands." << std::endl; + } - celix::ServiceTrackerOptions<celix::ShellCommandFunction> opts2{}; - opts2.updateWithProperties = [this](std::vector<std::tuple<celix::ShellCommandFunction*,const celix::Properties*>> services) { - std::lock_guard<std::mutex> lck(commands.mutex); - commands.commandFunctions = std::move(services); - }; - trk2 = ctx->trackFunctionServices(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, opts2); - } - bool executeCommand(const std::string &commandLine, std::ostream &out, std::ostream &) noexcept override { - out << "TODO call command '" << commandLine << "'" << std::endl; - return false; + return commandCalled; } private: std::shared_ptr<celix::IBundleContext> ctx; - - celix::ServiceTracker trk1{}; - celix::ServiceTracker trk2{}; - - struct { - mutable std::mutex mutex{}; - std::vector<std::tuple<celix::IShellCommand*, const celix::Properties*>> commands; - std::vector<std::tuple<celix::ShellCommandFunction*, const celix::Properties*>> commandFunctions; - } commands{}; }; class ShellBundleActivator : public celix::IBundleActivator { public: - bool start(std::shared_ptr<celix::IBundleContext> ctx) noexcept override { - //TODO ensure fixed framework thread that call bundle activators - registrations.push_back(registerLb(ctx)); - registrations.push_back(registerHelp(ctx)); + ShellBundleActivator(std::shared_ptr<celix::IBundleContext> ctx) { + //TODO ensure fixed framework thread that call ctor/dtor bundle activators + registrations.push_back(impl::registerLb(ctx)); + registrations.push_back(impl::registerHelp(ctx)); + registrations.push_back(impl::registerStop(ctx)); + registrations.push_back(impl::registerStart(ctx)); + registrations.push_back(impl::registerInspect(ctx)); registrations.push_back(ctx->registerService(std::shared_ptr<celix::IShell>{new Shell{ctx}})); - - return true; - } - - bool stop(std::shared_ptr<celix::IBundleContext>) noexcept override { - registrations.clear(); - return true; } private: - std::vector<celix::ServiceRegistration> registrations; + std::vector<celix::ServiceRegistration> registrations{}; }; -} -__attribute__((constructor)) -static void registerShellBundle() { - celix::StaticBundleOptions opts{}; - opts.bundleActivatorFactory = [](){ - return new ShellBundleActivator{}; - }; - opts.manifest[celix::MANIFEST_BUNDLE_VERSION] = "1.0.0"; - celix::registerStaticBundle("celix::Shell", opts); -} \ No newline at end of file + __attribute__((constructor)) + static void registerShellBundle() { + celix::Properties manifest{}; + manifest[celix::MANIFEST_BUNDLE_NAME] = "Shell"; + manifest[celix::MANIFEST_BUNDLE_GROUP] = "Celix"; + manifest[celix::MANIFEST_BUNDLE_VERSION] = "1.0.0"; + celix::registerStaticBundle<ShellBundleActivator>("celix::Shell", manifest); + } +} diff --git a/bundles/shell/cxx_shell/src/StopAndStartCommand.cc b/bundles/shell/cxx_shell/src/StopAndStartCommand.cc new file mode 100644 index 0000000..aae5a69 --- /dev/null +++ b/bundles/shell/cxx_shell/src/StopAndStartCommand.cc @@ -0,0 +1,75 @@ +/** + *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 "commands.h" + +#include <functional> + +#include "celix/api.h" +#include "celix/IShellCommand.h" + +namespace { + + void stopOrStart(std::shared_ptr<celix::IBundleContext> ctx, const std::string &cmdName, const std::vector<std::string> &cmdArgs, std::ostream &out, std::ostream &err) { + if (cmdArgs.empty()) { + out << "Provide a bundle id name to " << cmdName << std::endl; + } else { + for (const auto &bndId : cmdArgs) { + bool isNum = true; + for(const char &c : bndId) { + isNum = isNum && isdigit(c); + } + + if (isNum) { + long id = atoi(bndId.c_str()); + if (cmdName == "stop") { + ctx->stopBundle(id); + } else { + ctx->startBundle(id); + } + } else { + err << "Cannot parse '" << bndId << "' to bundle id" << std::endl; + } + } + } + } +} + + +celix::ServiceRegistration impl::registerStop(std::shared_ptr<celix::IBundleContext> ctx) { + using namespace std::placeholders; + celix::ShellCommandFunction stop = std::bind(&stopOrStart, ctx, _1, _2, _3, _4); + + celix::Properties props{}; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME] = "stop"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE] = "stop (bndId)+"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION] = "Stops the provided bundles, identified by the bundle ids"; + return ctx->registerFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, std::move(stop), std::move(props)); +} + +celix::ServiceRegistration impl::registerStart(std::shared_ptr<celix::IBundleContext> ctx) { + using namespace std::placeholders; + celix::ShellCommandFunction stop = std::bind(&stopOrStart, ctx, _1, _2, _3, _4); + + celix::Properties props{}; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_NAME] = "start"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_USAGE] = "start (bndId)+"; + props[celix::SHELL_COMMAND_FUNCTION_COMMAND_DESCRIPTION] = "Starts the provided bundles, identified by the bundle ids"; + return ctx->registerFunctionService(celix::SHELL_COMMAND_FUNCTION_SERVICE_FQN, std::move(stop), std::move(props)); +} \ No newline at end of file diff --git a/libs/framework_cxx/include/celix/IBundleActivator.h b/bundles/shell/cxx_shell/src/commands.h similarity index 53% copy from libs/framework_cxx/include/celix/IBundleActivator.h copy to bundles/shell/cxx_shell/src/commands.h index 1196e99..5f641b8 100644 --- a/libs/framework_cxx/include/celix/IBundleActivator.h +++ b/bundles/shell/cxx_shell/src/commands.h @@ -17,22 +17,21 @@ *under the License. */ -#ifndef CXX_CELIX_IBUNDLEACTIVATOR_H -#define CXX_CELIX_IBUNDLEACTIVATOR_H +#ifndef CELIX_COMMANDS_H +#define CELIX_COMMANDS_H -#include <memory> +#include "celix/api.h" -#include "IBundleContext.h" +namespace impl { + celix::ServiceRegistration registerLb(std::shared_ptr<celix::IBundleContext> ctx); + celix::ServiceRegistration registerHelp(std::shared_ptr<celix::IBundleContext> ctx); + celix::ServiceRegistration registerStop(std::shared_ptr<celix::IBundleContext> ctx); + celix::ServiceRegistration registerStart(std::shared_ptr<celix::IBundleContext> ctx); + celix::ServiceRegistration registerInspect(std::shared_ptr<celix::IBundleContext> ctx); -namespace celix { - class IBundleActivator { - public: - virtual ~IBundleActivator() = default; - - virtual bool resolve(std::shared_ptr<celix::IBundleContext> /*ctx*/) noexcept { return true; }; - virtual bool start(std::shared_ptr<celix::IBundleContext> ctx) noexcept = 0; - virtual bool stop(std::shared_ptr<celix::IBundleContext> /*ctx*/) noexcept { return true; } - }; + //query services, trackers & meta trackers TODO + // celix::ServiceRegistration registerQuery(std::shared_ptr<celix::IBundleContext> ctx); } -#endif //CXX_CELIX_IBUNDLEACTIVATOR_H + +#endif //CELIX_COMMANDS_H diff --git a/bundles/shell/cxx_shell/CMakeLists.txt b/bundles/shell/cxx_shell_tui/CMakeLists.txt similarity index 60% copy from bundles/shell/cxx_shell/CMakeLists.txt copy to bundles/shell/cxx_shell_tui/CMakeLists.txt index 070f05e..df20046 100644 --- a/bundles/shell/cxx_shell/CMakeLists.txt +++ b/bundles/shell/cxx_shell_tui/CMakeLists.txt @@ -17,21 +17,22 @@ find_package(glog REQUIRED) -#TODO rename to celix::shell && celix::shell_api +#TODO rename to celix::shell_tui -add_library(shell_api_cxx INTERFACE) -target_include_directories(shell_api INTERFACE - $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include> - $<INSTALL_INTERFACE:include/celix/shell> -) - -add_library(celix_shell_cxx STATIC - src/ShellActivator.cc - include/celix/IShell.h) -target_include_directories(celix_shell_cxx PRIVATE src) -target_include_directories(celix_shell_cxx PUBLIC include) -target_link_libraries(celix_shell_cxx PRIVATE glog::glog celix_framework_cxx) +#OR static lib, but then with all symbols to force constructor attribute +add_library(celix_cxx_shell_tui SHARED + src/ShellTuiActivator.cc + src/shell_test.cc) +target_include_directories(celix_cxx_shell_tui PRIVATE src) +target_link_libraries(celix_cxx_shell_tui PRIVATE celix_cxx_shell_api) +target_link_libraries(celix_cxx_shell_tui PUBLIC glog::glog celix_framework_cxx) #if (ENABLE_TESTING) # add_subdirectory(gtest) -#endif () \ No newline at end of file +#endif () + + +add_executable(shell_test + src/shell_test.cc +) +target_link_libraries(shell_test PRIVATE celix_cxx_shell celix_cxx_shell_tui) \ No newline at end of file diff --git a/bundles/shell/CMakeLists.txt b/bundles/shell/cxx_shell_tui/gtest/CMakeLists.txt similarity index 66% copy from bundles/shell/CMakeLists.txt copy to bundles/shell/cxx_shell_tui/gtest/CMakeLists.txt index 2fd81dc..b4a7204 100644 --- a/bundles/shell/CMakeLists.txt +++ b/bundles/shell/cxx_shell_tui/gtest/CMakeLists.txt @@ -5,9 +5,9 @@ # 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 @@ -15,9 +15,11 @@ # specific language governing permissions and limitations # under the License. -add_subdirectory(shell) -add_subdirectory(remote_shell) -add_subdirectory(shell_bonjour) -add_subdirectory(shell_tui) +set(SOURCES + src/main.cc +) +add_executable(celix_shell_cxx_tests ${SOURCES}) +target_link_libraries(celix_shell_cxx_tests PRIVATE gtest celix_framework_cxx celix_shell_cxx) -add_subdirectory(cxx_shell) +add_test(NAME celix_shell_cxx_tests COMMAND celix_shell_cxx_tests) +SETUP_TARGET_FOR_COVERAGE(celix_shell_cxx_tests_cov celix_shell_cxx_tests ${CMAKE_BINARY_DIR}/coverage/celix_shell_cxx_tests/celix_shell_cxx_tests) \ No newline at end of file diff --git a/bundles/shell/cxx_shell/include/celix/IShell.h b/bundles/shell/cxx_shell_tui/gtest/src/main.cc similarity index 66% copy from bundles/shell/cxx_shell/include/celix/IShell.h copy to bundles/shell/cxx_shell_tui/gtest/src/main.cc index 9809ac5..a76daa7 100644 --- a/bundles/shell/cxx_shell/include/celix/IShell.h +++ b/bundles/shell/cxx_shell_tui/gtest/src/main.cc @@ -17,20 +17,17 @@ *under the License. */ -#ifndef CXX_CELIX_ISHELL_H -#define CXX_CELIX_ISHELL_H +#include <gtest/gtest.h> +#include <glog/logging.h> -#include <iostream> +int main(int argc, char **argv) { + google::InitGoogleLogging(argv[0]); + google::LogToStderr(); -namespace celix { - class IShell { - public: - static constexpr const char * const SERVICE_FQN = "celix::IShell [Version 1]"; + ::testing::InitGoogleTest(&argc, argv); + int rc = RUN_ALL_TESTS(); - virtual ~IShell() = default; + google::ShutdownGoogleLogging(); - virtual bool executeCommand(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept = 0; - }; -} - -#endif //CXX_CELIX_ISHELL_H + return rc; +} \ No newline at end of file diff --git a/bundles/shell/cxx_shell_tui/src/ShellTuiActivator.cc b/bundles/shell/cxx_shell_tui/src/ShellTuiActivator.cc new file mode 100644 index 0000000..dc71faa --- /dev/null +++ b/bundles/shell/cxx_shell_tui/src/ShellTuiActivator.cc @@ -0,0 +1,150 @@ +/** + *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 <thread> +#include <cstdio> +#include <unistd.h> +#include <fcntl.h> + +#include <glog/logging.h> + +#include "celix/api.h" +#include "celix/IShellCommand.h" +#include "celix/IShell.h" + +static constexpr int LINE_SIZE = 256; +static constexpr const char * const PROMPT = "-> "; + +static constexpr int KEY_ENTER = '\n'; + +namespace { + + class ShellTui { + public: + ShellTui() { + int fds[2]; + int rc = pipe(fds); + if (rc == 0) { + readPipeFd = fds[0]; + writePipeFd = fds[1]; + if(fcntl(writePipeFd, F_SETFL, O_NONBLOCK) == 0) { + readThread = std::thread{&ShellTui::runnable, this}; + } else { + LOG(ERROR) << "fcntl on pipe failed" << std::endl; + } + } else { + LOG(ERROR) << "fcntl on pipe failed" << std::endl; + } + } + + ~ShellTui() { + write(writePipeFd, "\0", 1); //trigger select to stop + readThread.join(); + } + + void runnable() { + //setup file descriptors + fd_set rfds; + int nfds = writePipeFd > STDIN_FILENO ? (writePipeFd +1) : (STDIN_FILENO + 1); + + for (;;) { + writePrompt(); + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + FD_SET(readPipeFd, &rfds); + + if (select(nfds, &rfds, NULL, NULL, NULL) > 0) { + if (FD_ISSET(readPipeFd, &rfds)) { + break; //something is written to the pipe -> exit thread + } else if (FD_ISSET(STDIN_FILENO, &rfds)) { + parseInput(); + } + } + } + } + + void writePrompt() { + std::cout << PROMPT; + std::flush(std::cout); + } + + void parseInput() { + char* line = NULL; + int nr_chars = (int)read(STDIN_FILENO, buffer, LINE_SIZE-pos-1); + for (int bufpos = 0; bufpos < nr_chars; bufpos++) { + if (buffer[bufpos] == KEY_ENTER) { //end of line -> forward command + line = in; // todo trim string + std::lock_guard<std::mutex> lck{mutex}; + if (shell) { + shell->executeCommandLine(line, std::cout, std::cerr); + } else { + std::cerr << "Shell service not available\n"; + } + pos = 0; + in[pos] = '\0'; + } else { //text + in[pos] = buffer[bufpos]; + in[pos + 1] = '\0'; + pos++; + continue; + } + } //for + } + + void setShell(std::shared_ptr<celix::IShell> _shell) { + std::lock_guard<std::mutex> lck{mutex}; + shell = _shell; + } + private: + std::mutex mutex{}; + std::shared_ptr<celix::IShell> shell{}; + + std::thread readThread; + + int readPipeFd; + int writePipeFd; + + + char in[LINE_SIZE+1]{}; + char buffer[LINE_SIZE+1]{}; + int pos{}; + }; + + + class ShellTuiBundleActivator : public celix::IBundleActivator { + public: + ShellTuiBundleActivator(std::shared_ptr<celix::IBundleContext> ctx) { + celix::ServiceTrackerOptions<celix::IShell> opts{}; + opts.set = std::bind(&ShellTui::setShell, &tui, std::placeholders::_1); + trk = ctx->trackServices(opts); + } + private: + ShellTui tui{}; + celix::ServiceTracker trk; + }; + + __attribute__((constructor)) + static void registerShellBundle() { + celix::Properties manifest{}; + manifest[celix::MANIFEST_BUNDLE_NAME] = "Shell Tui"; + manifest[celix::MANIFEST_BUNDLE_GROUP] = "Celix"; + manifest[celix::MANIFEST_BUNDLE_VERSION] = "1.0.0"; + celix::registerStaticBundle<ShellTuiBundleActivator>("celix::ShellTui", manifest); + } +} \ No newline at end of file diff --git a/bundles/shell/cxx_shell/include/celix/IShell.h b/bundles/shell/cxx_shell_tui/src/shell_test.cc similarity index 68% copy from bundles/shell/cxx_shell/include/celix/IShell.h copy to bundles/shell/cxx_shell_tui/src/shell_test.cc index 9809ac5..cd3af3c 100644 --- a/bundles/shell/cxx_shell/include/celix/IShell.h +++ b/bundles/shell/cxx_shell_tui/src/shell_test.cc @@ -17,20 +17,19 @@ *under the License. */ -#ifndef CXX_CELIX_ISHELL_H -#define CXX_CELIX_ISHELL_H - #include <iostream> -namespace celix { - class IShell { - public: - static constexpr const char * const SERVICE_FQN = "celix::IShell [Version 1]"; +#include <glog/logging.h> + +#include "celix/api.h" - virtual ~IShell() = default; - virtual bool executeCommand(const std::string &commandLine, std::ostream &out, std::ostream &err) noexcept = 0; - }; -} +int main(int /*argc*/, char **argv) { + google::InitGoogleLogging(argv[0]); + google::LogToStderr(); -#endif //CXX_CELIX_ISHELL_H + auto fw = celix::Framework{}; + std::cout << "Waiting for Framework shutdown\n"; + fw.waitForShutdown(); + return 0; +} \ No newline at end of file diff --git a/cmake/celix_project/AddGTest.cmake b/cmake/celix_project/AddGTest.cmake index a1753a8..93227c1 100644 --- a/cmake/celix_project/AddGTest.cmake +++ b/cmake/celix_project/AddGTest.cmake @@ -30,7 +30,7 @@ file(MAKE_DIRECTORY ${source_dir}/googletest/include) add_library(gtest IMPORTED STATIC GLOBAL) add_dependencies(gtest googletest_project) set_target_properties(gtest PROPERTIES - IMPORTED_LOCATION "${binary_dir}/lib/libgtest.a" + IMPORTED_LOCATION "${binary_dir}/googlemock/gtest/libgtest.a" INTERFACE_INCLUDE_DIRECTORIES "${source_dir}/googletest/include" ) @@ -38,6 +38,6 @@ file(MAKE_DIRECTORY ${source_dir}/googlemock/include) add_library(gmock IMPORTED STATIC GLOBAL) add_dependencies(gmock googletest_project) set_target_properties(gmock PROPERTIES - IMPORTED_LOCATION "${binary_dir}/lib/libgmock.a" + IMPORTED_LOCATION "${binary_dir}/googlemock/libgmock.a" INTERFACE_INCLUDE_DIRECTORIES "${source_dir}/googlemock/include" ) diff --git a/libs/framework_cxx/gtest/src/Framework_tests.cc b/libs/framework_cxx/gtest/src/Framework_tests.cc index 34cad54..7afd9a1 100644 --- a/libs/framework_cxx/gtest/src/Framework_tests.cc +++ b/libs/framework_cxx/gtest/src/Framework_tests.cc @@ -44,80 +44,71 @@ TEST_F(FrameworkTest, CreateDestroy) { EXPECT_TRUE(isFramework); } -TEST_F(FrameworkTest, InstallBundle) { +class EmbeddedActivator : public celix::IBundleActivator { +public: + EmbeddedActivator(std::shared_ptr<celix::IBundleContext>) { + startCount++; + } - class EmbeddedActivator : public celix::IBundleActivator { - public: - virtual ~EmbeddedActivator() = default; + virtual ~EmbeddedActivator() { + stopCount++; + } - bool resolve(std::shared_ptr<celix::IBundleContext> ctx) noexcept override { - EXPECT_GE(ctx->bundle()->id(), 1); - resolveCalled = true; - return true; - } - - bool start(std::shared_ptr<celix::IBundleContext>) noexcept override { - startCalled = true; - return true; - } - - bool stop(std::shared_ptr<celix::IBundleContext>) noexcept override { - stopCalled = true; - return true; - } - - bool resolveCalled = false; - bool startCalled = false; - bool stopCalled = false; - }; + static std::atomic<int> startCount; + static std::atomic<int> stopCount; +}; - long bndId1 = framework().installBundle<EmbeddedActivator>("embedded"); +std::atomic<int> EmbeddedActivator::startCount{0}; +std::atomic<int> EmbeddedActivator::stopCount{0}; + +TEST_F(FrameworkTest, InstallBundle) { + EmbeddedActivator::startCount = 0; + EmbeddedActivator::stopCount = 0; + + auto actFactory = [](std::shared_ptr<celix::IBundleContext> ctx) -> celix::IBundleActivator* { + return new EmbeddedActivator{std::move(ctx)}; + }; + long bndId1 = framework().installBundle("embedded", actFactory); EXPECT_GE(bndId1, 0); + EXPECT_EQ(1, EmbeddedActivator::startCount); + EXPECT_EQ(0, EmbeddedActivator::stopCount); - std::shared_ptr<EmbeddedActivator> act{new EmbeddedActivator}; - long bndId2 = framework().installBundle("embedded2", act); + long bndId2 = framework().installBundle<EmbeddedActivator>("embedded2"); EXPECT_GE(bndId2, 0); EXPECT_NE(bndId1, bndId2); - EXPECT_TRUE(act->resolveCalled); - EXPECT_TRUE(act->startCalled); - EXPECT_FALSE(act->stopCalled); + EXPECT_EQ(2, EmbeddedActivator::startCount); + EXPECT_EQ(0, EmbeddedActivator::stopCount); framework().stopBundle(bndId2); - EXPECT_TRUE(act->stopCalled); + EXPECT_EQ(1, EmbeddedActivator::stopCount); - std::shared_ptr<EmbeddedActivator> act3{new EmbeddedActivator}; { celix::Framework fw{}; - fw.installBundle("embedded3", act3); - EXPECT_TRUE(act3->resolveCalled); - EXPECT_TRUE(act3->startCalled); - EXPECT_FALSE(act3->stopCalled); + fw.installBundle<EmbeddedActivator>("embedded3"); + EXPECT_EQ(3, EmbeddedActivator::startCount); + EXPECT_EQ(1, EmbeddedActivator::stopCount); //NOTE fw out of scope -> bundle stopped } - EXPECT_TRUE(act3->stopCalled); + EXPECT_EQ(3, EmbeddedActivator::startCount); + EXPECT_EQ(2, EmbeddedActivator::stopCount); } TEST_F(FrameworkTest, StaticBundleTest) { class EmbeddedActivator : public celix::IBundleActivator { public: + EmbeddedActivator() {} virtual ~EmbeddedActivator() = default; - - bool start(std::shared_ptr<celix::IBundleContext>) noexcept override { - return true; - } }; int count = 0; - auto factory = [&]() -> celix::IBundleActivator * { + auto factory = [&](std::shared_ptr<celix::IBundleContext>) -> celix::IBundleActivator * { count++; return new EmbeddedActivator{}; }; EXPECT_EQ(0, framework().listBundles().size()); //no bundles installed; - celix::StaticBundleOptions opts; - opts.bundleActivatorFactory = std::move(factory); - celix::registerStaticBundle("static", opts); + celix::registerStaticBundle("static", factory); EXPECT_EQ(1, framework().listBundles().size()); //static bundle instance installed EXPECT_EQ(1, count); diff --git a/libs/framework_cxx/include/celix/Framework.h b/libs/framework_cxx/include/celix/Framework.h index 60b6fca..a2d9b13 100644 --- a/libs/framework_cxx/include/celix/Framework.h +++ b/libs/framework_cxx/include/celix/Framework.h @@ -30,25 +30,25 @@ namespace celix { - struct StaticBundleOptions { - std::string name{}; - std::string group{}; - std::string version{}; - celix::Properties manifest{}; - - std::function<celix::IBundleActivator*()> bundleActivatorFactory{}; - - //TODO resources. poiting to bundle specific symbols which is linked zip file - char * const resoucreZip = nullptr; - size_t resourceZipLength = 0; - }; - - void registerStaticBundle(std::string symbolicName, const StaticBundleOptions &opts); - //TODO useFrameworks with a callback with as argument a fw ref + //TODO resources. resolved from bundle specific symbols which is linked zip file to the library + void registerStaticBundle( + std::string symbolicName, + std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> bundleActivatorFactory = {}, + celix::Properties manifest = {}); + + template<typename T> + void registerStaticBundle( + std::string symbolicName, + celix::Properties manifest = {}) { + auto actFactory = [](std::shared_ptr<celix::IBundleContext> ctx) { + return new T{std::move(ctx)}; + }; + celix::registerStaticBundle(std::move(symbolicName), actFactory, std::move(manifest)); + } class Framework { public: - Framework(); + Framework(celix::Properties config = {}); ~Framework(); Framework(Framework &&rhs); Framework& operator=(Framework&& rhs); @@ -56,14 +56,15 @@ namespace celix { Framework(const Framework& rhs) = delete; Framework& operator=(const Framework &rhs) = delete; - template<typename T> - long installBundle(std::string symbolicName, celix::Properties manifest = {}, bool autoStart = true) { - std::shared_ptr<celix::IBundleActivator> activator{new T{}}; - return installBundle(std::move(symbolicName), std::move(activator), std::move(manifest), autoStart); + long installBundle(std::string name, celix::Properties manifest = {}, bool autoStart = true) { + auto actFactory = [](std::shared_ptr<celix::IBundleContext> ctx) { + return new T{std::move(ctx)}; + }; + return installBundle(name, std::move(actFactory), manifest, autoStart); } - long installBundle(std::string symbolicName, std::shared_ptr<celix::IBundleActivator> activator, celix::Properties manifest = {}, bool autoStart = true); + long installBundle(std::string name, std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> actFactory, celix::Properties manifest = {}, bool autoStart = true); //long installBundle(const std::string &path); @@ -78,6 +79,8 @@ namespace celix { std::vector<long> listBundles(bool includeFrameworkBundle = false) const; celix::ServiceRegistry& registry(const std::string &lang); + + bool waitForShutdown() const; private: class Impl; std::unique_ptr<Impl> pimpl; diff --git a/libs/framework_cxx/include/celix/IBundle.h b/libs/framework_cxx/include/celix/IBundle.h index a8d0de1..9f2e8a8 100644 --- a/libs/framework_cxx/include/celix/IBundle.h +++ b/libs/framework_cxx/include/celix/IBundle.h @@ -27,7 +27,6 @@ namespace celix { enum class BundleState { INSTALLED, - RESOLVED, ACTIVE, }; diff --git a/libs/framework_cxx/include/celix/IBundleActivator.h b/libs/framework_cxx/include/celix/IBundleActivator.h index 1196e99..a3ccd92 100644 --- a/libs/framework_cxx/include/celix/IBundleActivator.h +++ b/libs/framework_cxx/include/celix/IBundleActivator.h @@ -25,13 +25,17 @@ #include "IBundleContext.h" namespace celix { + /** + * The BundleActivator. + * + * This is a marker interface and contains no virtual methods. + * + * The Celix Framework will expect a constructor with a std::shared_ptr<celix::IBundleContext> argument on the + * contrete bundle activator. RAII will be used to start (on ctor) and stop (on dtor) a bundle. + */ class IBundleActivator { public: virtual ~IBundleActivator() = default; - - virtual bool resolve(std::shared_ptr<celix::IBundleContext> /*ctx*/) noexcept { return true; }; - virtual bool start(std::shared_ptr<celix::IBundleContext> ctx) noexcept = 0; - virtual bool stop(std::shared_ptr<celix::IBundleContext> /*ctx*/) noexcept { return true; } }; } diff --git a/libs/framework_cxx/include/celix/IBundleContext.h b/libs/framework_cxx/include/celix/IBundleContext.h index e77623a..553372a 100644 --- a/libs/framework_cxx/include/celix/IBundleContext.h +++ b/libs/framework_cxx/include/celix/IBundleContext.h @@ -51,6 +51,10 @@ namespace celix { virtual bool useBundle(long bndId, std::function<void(const celix::IBundle &bnd)> use) const noexcept = 0; virtual int useBundles(std::function<void(const celix::IBundle &bnd)> use, bool includeFrameworkBundle = true) const noexcept = 0; + virtual bool stopBundle(long bndId) noexcept = 0; + virtual bool startBundle(long bndId) noexcept = 0; + //TODO install / uninstall bundles + template<typename I> bool useService(std::function<void(I &svc)> use, const std::string &filter = "") const noexcept { return registry().useService<I>(std::move(use), filter, bundle()); @@ -128,7 +132,8 @@ namespace celix { //TODO track trackers //TODO track c trackers - private: + + virtual celix::ServiceRegistry& registry() const noexcept = 0; virtual celix::ServiceRegistry& cRegistry() const noexcept = 0; }; diff --git a/libs/framework_cxx/src/BundleImpl.h b/libs/framework_cxx/src/BundleImpl.h index ab49636..dd2c53f 100644 --- a/libs/framework_cxx/src/BundleImpl.h +++ b/libs/framework_cxx/src/BundleImpl.h @@ -47,20 +47,29 @@ namespace impl { int useBundles(std::function<void(const celix::IBundle &bnd)> use, bool includeFrameworkBundle = true) const noexcept override { return bnd->framework().useBundles(std::move(use), includeFrameworkBundle); } + + bool stopBundle(long bndId) noexcept override { + return bnd->framework().stopBundle(bndId); + } + + bool startBundle(long bndId) noexcept override { + return bnd->framework().startBundle(bndId); + } + private: celix::ServiceRegistry& registry() const noexcept override { return *reg; } celix::ServiceRegistry& cRegistry() const noexcept override { return *cReg; } const std::shared_ptr<celix::IBundle> bnd; - const std::shared_ptr<celix::ServiceRegistry> reg; - const std::shared_ptr<celix::ServiceRegistry> cReg; + celix::ServiceRegistry * const reg; //TODO make weak_ptr + celix::ServiceRegistry * const cReg; //TODO make weak_ptr }; class Bundle : public celix::IBundle { public: - Bundle(long _bndId, celix::Framework *_fw, celix::Properties _manifest, std::shared_ptr<celix::IBundleActivator> _activator) : - bndId{_bndId}, fw{_fw}, bndManifest{std::move(_manifest)}, activator{std::move(_activator)} { + Bundle(long _bndId, celix::Framework *_fw, celix::Properties _manifest) : + bndId{_bndId}, fw{_fw}, bndManifest{std::move(_manifest)} { bndState.store(BundleState::INSTALLED, std::memory_order_release); } @@ -100,7 +109,6 @@ namespace impl { const long bndId; celix::Framework * const fw; const celix::Properties bndManifest; - const std::shared_ptr<celix::IBundleActivator> activator; std::weak_ptr<celix::IBundleContext> context; std::atomic<BundleState> bndState; @@ -109,10 +117,10 @@ namespace impl { class BundleController { public: BundleController( - std::shared_ptr<celix::IBundleActivator> _act, + std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> _actFactory, std::shared_ptr<celix::impl::Bundle> _bnd, std::shared_ptr<celix::impl::BundleContext> _ctx) : - act{std::move(_act)}, bnd{std::move(_bnd)}, ctx{std::move(_ctx)} {} + actFactory{std::move(_actFactory)}, bnd{std::move(_bnd)}, ctx{std::move(_ctx)} {} //specific part bool transitionTo(BundleState desired) { @@ -122,39 +130,14 @@ namespace impl { if (state == desired) { //nop success = true; - } else if (state == BundleState::INSTALLED && desired == BundleState::RESOLVED) { - //TODO create cache dir for bundle - bool resolved = act->resolve(ctx); - if (resolved) { - bnd->setState(BundleState::RESOLVED); - success = true; - } else { - LOG(WARNING) << "Transition to resolved state for bundle " << bnd->symbolicName() << " (" << bnd->id() << ") failed." << std::endl; - } - - } else if (state == BundleState::RESOLVED && desired == BundleState::ACTIVE) { - bool started = act->start(ctx); - if (started) { - bnd->setState(BundleState::ACTIVE); - success = true; - } else { - LOG(WARNING) << "Transition to active state for bundle " << bnd->symbolicName() << " (" << bnd->id() << ") failed." << std::endl; - } - } else if (state == BundleState::ACTIVE && desired == BundleState::RESOLVED ) { - bool stopped = act->stop(ctx); - if (stopped) { - bnd->setState(BundleState::RESOLVED); - success = true; - - //TODO use custom deleter to check this (use_count call is a race condition) - bool unique = ctx.use_count() == 1; - if (!unique) { - LOG(WARNING) << "Bundle Context is not unique. "; - LOG(WARNING) << "Check if there are still some dangling references to the context of the stopped bundle." << std::endl; - } - } else { - LOG(WARNING) << "Transition to resolved state for bundle " << bnd->symbolicName() << " (" << bnd->id() << ") failed." << std::endl; - } + } else if (state == BundleState::INSTALLED && desired == BundleState::ACTIVE) { + act = std::unique_ptr<celix::IBundleActivator>{actFactory(ctx)}; + bnd->setState(BundleState::ACTIVE); + success = true; + } else if (state == BundleState::ACTIVE && desired == BundleState::INSTALLED ) { + act = nullptr; + bnd->setState(BundleState::INSTALLED); + success = true; } else { //LOG(ERROR) << "Unexpected desired state " << desired << " from state " << bndState << std::endl; LOG(ERROR) << "Unexpected desired/form state combination " << std::endl; @@ -162,15 +145,15 @@ namespace impl { return success; } - std::shared_ptr<celix::IBundleActivator> activator() const { return act; } std::shared_ptr<celix::impl::Bundle> bundle() const { return bnd; } std::shared_ptr<celix::impl::BundleContext> context() const { return ctx; } private: - const std::shared_ptr<celix::IBundleActivator> act; + const std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> actFactory; const std::shared_ptr<celix::impl::Bundle> bnd; const std::shared_ptr<celix::impl::BundleContext> ctx; mutable std::mutex mutex{}; + std::unique_ptr<celix::IBundleActivator> act{nullptr}; }; } }; diff --git a/libs/framework_cxx/src/Framework.cc b/libs/framework_cxx/src/Framework.cc index a224720..cb5b6ed 100644 --- a/libs/framework_cxx/src/Framework.cc +++ b/libs/framework_cxx/src/Framework.cc @@ -26,6 +26,7 @@ #include <iostream> #include <set> #include <vector> +#include <future> #include <glog/logging.h> @@ -36,7 +37,7 @@ struct StaticBundleEntry { const std::string symbolicName; const celix::Properties manifest; - const std::function<celix::IBundleActivator*()> activatorFactory; + const std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> activatorFactory; }; static struct { @@ -51,10 +52,11 @@ static void unregisterFramework(celix::Framework *fw); class celix::Framework::Impl : public IBundle { public: - Impl(celix::Framework *_fw) : fw{_fw}, bndManifest{createManifest()}, cwd{createCwd()} {} + Impl(celix::Framework *_fw, celix::Properties _config) : fw{_fw}, config{std::move(_config)}, bndManifest{createManifest()}, cwd{createCwd()} {} ~Impl() { stopFramework(); + waitForShutdown(); } std::vector<long> listBundles(bool includeFrameworkBundle) const { @@ -70,9 +72,15 @@ public: return result; } - long installBundle(std::string symbolicName, std::shared_ptr<celix::IBundleActivator> activator, celix::Properties manifest, bool autoStart) { + long installBundle(std::string symbolicName, std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> actFactory, celix::Properties manifest, bool autoStart) { + //TODO if activator is nullptr -> use empty activator //TODO on separate thread ?? specific bundle resolve thread ?? long bndId = -1L; + if (symbolicName.empty()) { + LOG(WARNING) << "Cannot install bundle with a empty symbolic name" << std::endl; + return bndId; + } + std::shared_ptr<celix::impl::BundleController> bndController{nullptr}; { manifest[celix::MANIFEST_BUNDLE_SYMBOLIC_NAME] = symbolicName; @@ -88,9 +96,9 @@ public: std::lock_guard<std::mutex> lck{bundles.mutex}; bndId = bundles.nextBundleId++; - auto bnd = std::shared_ptr<celix::impl::Bundle>{new celix::impl::Bundle{bndId, this->fw, std::move(manifest), std::move(activator)}}; + auto bnd = std::shared_ptr<celix::impl::Bundle>{new celix::impl::Bundle{bndId, this->fw, std::move(manifest)}}; auto ctx = std::shared_ptr<celix::impl::BundleContext>{new celix::impl::BundleContext{bnd}}; - bndController = std::shared_ptr<celix::impl::BundleController>{new celix::impl::BundleController{activator, bnd, ctx}}; + bndController = std::shared_ptr<celix::impl::BundleController>{new celix::impl::BundleController{std::move(actFactory), bnd, ctx}}; bundles.entries.emplace(std::piecewise_construct, std::forward_as_tuple(bndId), std::forward_as_tuple(bndController)); @@ -99,26 +107,32 @@ public: } if (bndController) { - bool successful = bndController->transitionTo(BundleState::RESOLVED); - if (successful && autoStart) { - successful = bndController->transitionTo(BundleState::ACTIVE); + if (autoStart) { + bool successful = bndController->transitionTo(BundleState::ACTIVE); if (!successful) { LOG(WARNING) << "Cannot start bundle " << bndController->bundle()->symbolicName() << std::endl; } - } else { - LOG(WARNING) << "Cannot resolve bundle " << bndController->bundle()->symbolicName() << std::endl; } - //TODO decrease bnd entry usage } + return bndId; } bool startBundle(long bndId) { - return transitionBundleTo(bndId, BundleState::ACTIVE); + if (bndId == this->fwBndId) { + //TODO + return false; + } else { + return transitionBundleTo(bndId, BundleState::ACTIVE); + } } bool stopBundle(long bndId) { - return transitionBundleTo(bndId, BundleState::RESOLVED); + if (bndId == this->fwBndId) { + return stopFramework(); + } else { + return transitionBundleTo(bndId, BundleState::INSTALLED); + } } bool uninstallBundle(long bndId) { @@ -134,11 +148,10 @@ public: } } if (removed) { - bool resolved = removed->transitionTo(BundleState::RESOLVED); - if (resolved) { - uninstalled = true; - bool unique = removed.unique(); - assert(unique); //TODO cond / wait ? + bool stopped = removed->transitionTo(BundleState::INSTALLED); + if (stopped) { + //TODO check and wait till bundle is not used anymore. is this needed (shared_ptr) or just let access + //to filesystem fail ... } else { //add bundle again -> not uninstalled std::lock_guard<std::mutex> lck{bundles.mutex}; @@ -245,15 +258,29 @@ public: celix::Framework& framework() const noexcept override { return *fw; } bool stopFramework() { - std::vector<long> bundles = listBundles(false); - while (!bundles.empty()) { - for (auto it = bundles.rbegin(); it != bundles.rend(); ++it) { - stopBundle(*it); - uninstallBundle(*it); - } - bundles = listBundles(false); + std::lock_guard<std::mutex> lck{shutdown.mutex}; + if (!shutdown.shutdownStarted) { + shutdown.future = std::async(std::launch::async, [this]{ + std::vector<long> bundles = listBundles(false); + while (!bundles.empty()) { + for (auto it = bundles.rbegin(); it != bundles.rend(); ++it) { + stopBundle(*it); + uninstallBundle(*it); + } + bundles = listBundles(false); + } + }); + shutdown.shutdownStarted = true; + shutdown.cv.notify_all(); } + return true; + } + bool waitForShutdown() const { + std::unique_lock<std::mutex> lck{shutdown.mutex}; + shutdown.cv.wait(lck, [this]{return this->shutdown.shutdownStarted;}); + shutdown.future.wait(); + lck.unlock(); return true; } private: @@ -275,10 +302,22 @@ private: } } + const long fwBndId = 1L; celix::Framework * const fw; + const celix::Properties config; const celix::Properties bndManifest; const std::string cwd; + + struct { + mutable std::mutex mutex{}; + mutable std::condition_variable cv{}; + std::future<void> future{}; + bool shutdownStarted = false; + } shutdown{}; + + + struct { std::unordered_map<long, std::shared_ptr<celix::impl::BundleController>> entries{}; long nextBundleId = 2; @@ -295,9 +334,9 @@ private: * Framework **********************************************************************************************************************/ -celix::Framework::Framework() { - pimpl = std::unique_ptr<Impl>{new Impl{this}}; - registerFramework(this); //TODO improve ugly.. maybe register impl.. but that is private -> so make register static member functions of impl... +celix::Framework::Framework(celix::Properties config) { + pimpl = std::unique_ptr<Impl>{new Impl{this, std::move(config)}}; + registerFramework(this); } celix::Framework::~Framework() { unregisterFramework(this); @@ -305,10 +344,12 @@ celix::Framework::~Framework() { celix::Framework::Framework(Framework &&rhs) = default; celix::Framework& celix::Framework::operator=(Framework&& rhs) = default; -long celix::Framework::installBundle(std::string name, std::shared_ptr<celix::IBundleActivator> activator, celix::Properties manifest, bool autoStart) { - return pimpl->installBundle(std::move(name), std::move(activator), std::move(manifest), autoStart); + +long celix::Framework::installBundle(std::string name, std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> actFactory, celix::Properties manifest, bool autoStart) { + return pimpl->installBundle(std::move(name), actFactory, std::move(manifest), autoStart); } + std::vector<long> celix::Framework::listBundles(bool includeFrameworkBundle) const { return pimpl->listBundles(includeFrameworkBundle); } bool celix::Framework::useBundle(long bndId, std::function<void(const celix::IBundle &bnd)> use) const { @@ -324,23 +365,28 @@ bool celix::Framework::stopBundle(long bndId) { return pimpl->stopBundle(bndId); bool celix::Framework::uninstallBundle(long bndId) { return pimpl->uninstallBundle(bndId); } celix::ServiceRegistry& celix::Framework::registry(const std::string &lang) { return pimpl->registry(lang); } +bool celix::Framework::waitForShutdown() const { return pimpl->waitForShutdown(); } + /*********************************************************************************************************************** * Celix 'global' functions **********************************************************************************************************************/ -void celix::registerStaticBundle(std::string symbolicName, const celix::StaticBundleOptions &opts) { +void celix::registerStaticBundle( + std::string symbolicName, + std::function<celix::IBundleActivator*(std::shared_ptr<celix::IBundleContext>)> bundleActivatorFactory, + celix::Properties manifest) { std::lock_guard<std::mutex> lck{staticRegistry.mutex}; - staticRegistry.bundles.emplace_back(StaticBundleEntry{.symbolicName = std::move(symbolicName), .manifest = opts.manifest, .activatorFactory = opts.bundleActivatorFactory}); for (auto fw : staticRegistry.frameworks) { - fw->installBundle(symbolicName, std::shared_ptr<celix::IBundleActivator>{opts.bundleActivatorFactory()}, opts.manifest); + fw->installBundle(symbolicName, bundleActivatorFactory, manifest); } + staticRegistry.bundles.emplace_back(StaticBundleEntry{.symbolicName = std::move(symbolicName), .manifest = std::move(manifest), .activatorFactory = std::move(bundleActivatorFactory)}); } static void registerFramework(celix::Framework *fw) { std::lock_guard<std::mutex> lck{staticRegistry.mutex}; staticRegistry.frameworks.insert(fw); for (auto &entry : staticRegistry.bundles) { - fw->installBundle(entry.symbolicName, std::shared_ptr<celix::IBundleActivator>{entry.activatorFactory()}, entry.manifest); + fw->installBundle(entry.symbolicName, entry.activatorFactory, entry.manifest); } } diff --git a/libs/registry/gtest/src/RegistryConcurrency_tests.cc b/libs/registry/gtest/src/RegistryConcurrency_tests.cc index c77c198..f7e86d3 100644 --- a/libs/registry/gtest/src/RegistryConcurrency_tests.cc +++ b/libs/registry/gtest/src/RegistryConcurrency_tests.cc @@ -29,8 +29,7 @@ public: celix::ServiceRegistry& registry() { return reg; } private: - celix::ServiceRegistry reg{"C/C++"}; -}; + celix::ServiceRegistry reg{"C++"}; class ICalc { public: diff --git a/libs/registry/gtest/src/Registry_tests.cc b/libs/registry/gtest/src/Registry_tests.cc index 8c00b11..9bbb899 100644 --- a/libs/registry/gtest/src/Registry_tests.cc +++ b/libs/registry/gtest/src/Registry_tests.cc @@ -30,7 +30,7 @@ public: celix::ServiceRegistry& registry() { return reg; } private: - celix::ServiceRegistry reg{"C/C++"}; + celix::ServiceRegistry reg{"C++"}; }; class MarkerInterface1 { @@ -264,6 +264,27 @@ TEST_F(RegistryTest, StdFunctionTest) { }); } +TEST_F(RegistryTest, ListServicesTest) { + std::vector<std::string> serviceNames = registry().listAllRegisteredServiceNames(); + EXPECT_EQ(0, serviceNames.size()); + + std::function<void()> nop = []{/*nop*/}; + class MarkerInterface1 {}; + MarkerInterface1 intf1; + + { + auto reg1 = registry().registerFunctionService("nop", nop); + serviceNames = registry().listAllRegisteredServiceNames(); + EXPECT_EQ(1, serviceNames.size()); + + auto reg2 = registry().registerService(intf1); + serviceNames = registry().listAllRegisteredServiceNames(); + EXPECT_EQ(2, serviceNames.size()); + } + serviceNames = registry().listAllRegisteredServiceNames(); + EXPECT_EQ(0, serviceNames.size()); +} + //TODO function use with props and bnd //TODO use with filter //TODO use with sync test (see BundleContext tests) \ No newline at end of file diff --git a/libs/registry/gtest/src/ServiceTracking_tests.cc b/libs/registry/gtest/src/ServiceTracking_tests.cc index 7a88f1b..035e8a9 100644 --- a/libs/registry/gtest/src/ServiceTracking_tests.cc +++ b/libs/registry/gtest/src/ServiceTracking_tests.cc @@ -28,7 +28,7 @@ class ServiceTrackingTest : public ::testing::Test { public: celix::ServiceRegistry& registry() { return reg; } private: - celix::ServiceRegistry reg{"C/C++"}; + celix::ServiceRegistry reg{"C++"}; }; class MarkerInterface1 { @@ -62,9 +62,9 @@ TEST_F(ServiceTrackingTest, CreateTrackersTest) { } TEST_F(ServiceTrackingTest, ServicesCountTrackersTest) { - MarkerInterface1 intf1; - MarkerInterface2 intf2; - MarkerInterface3 intf3; + MarkerInterface1 intf1{}; + MarkerInterface2 intf2{}; + MarkerInterface3 intf3{}; auto trk1 = registry().trackServices<MarkerInterface1>(); ASSERT_EQ(0, trk1.trackCount()); @@ -103,17 +103,17 @@ TEST_F(ServiceTrackingTest, ServicesCountTrackersTest) { } TEST_F(ServiceTrackingTest, SetServiceTest) { - MarkerInterface1 intf1; - MarkerInterface2 intf2; - MarkerInterface3 intf3; + MarkerInterface1 intf1{}; + MarkerInterface2 intf2{}; + MarkerInterface3 intf3{}; MarkerInterface1 *ptrToSvc = nullptr; //const celix::Properties *ptrToProps = nullptr; //const celix::IBundle *ptrToBnd = nullptr; celix::ServiceTrackerOptions<MarkerInterface1> opts{}; - opts.set = [&ptrToSvc](MarkerInterface1* svc) { - ptrToSvc = svc; + opts.set = [&ptrToSvc](std::shared_ptr<MarkerInterface1> svc) { + ptrToSvc = svc.get(); }; auto reg1 = registry().registerService(intf1); @@ -162,17 +162,17 @@ TEST_F(ServiceTrackingTest, SetServiceWithPropsAndOwnderTest) { } TEST_F(ServiceTrackingTest, AddRemoveTest) { - MarkerInterface1 intf1; - MarkerInterface2 intf2; - MarkerInterface3 intf3; + MarkerInterface1 intf1{}; + MarkerInterface2 intf2{}; + MarkerInterface3 intf3{}; - std::vector<MarkerInterface1*> services{}; + std::vector<std::shared_ptr<MarkerInterface1>> services{}; celix::ServiceTrackerOptions<MarkerInterface1> opts{}; - opts.add = [&services](MarkerInterface1* svc) { + opts.add = [&services](std::shared_ptr<MarkerInterface1> svc) { services.push_back(svc); }; - opts.remove = [&services](MarkerInterface1* svc) { + opts.remove = [&services](std::shared_ptr<MarkerInterface1> svc) { services.erase(std::remove(services.begin(), services.end(), svc), services.end()); }; @@ -182,24 +182,24 @@ TEST_F(ServiceTrackingTest, AddRemoveTest) { auto trk1 = registry().trackServices(opts); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf1, services[0]); //should be intf1 + EXPECT_EQ(&intf1, services[0].get()); //should be intf1 reg1.unregister(); EXPECT_EQ(0, services.size()); reg1 = registry().registerService(intf1); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf1, services[0]); //should be intf1 again + EXPECT_EQ(&intf1, services[0].get()); //should be intf1 again MarkerInterface1 intf4{}; auto reg4 = registry().registerService(intf4); ASSERT_EQ(2, services.size()); - EXPECT_EQ(&intf1, services[0]); - EXPECT_EQ(&intf4, services[1]); + EXPECT_EQ(&intf1, services[0].get()); + EXPECT_EQ(&intf4, services[1].get()); reg1.unregister(); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf4, services[0]); //intf1 gone -> index 0: intf4 + EXPECT_EQ(&intf4, services[0].get()); //intf1 gone -> index 0: intf4 trk1.stop(); EXPECT_EQ(0, services.size()); @@ -207,7 +207,7 @@ TEST_F(ServiceTrackingTest, AddRemoveTest) { { auto trk2 = registry().trackServices(opts); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf4, services[0]); + EXPECT_EQ(&intf4, services[0].get()); //out of scope -> tracker stopped } EXPECT_EQ(0, services.size()); //stop tracking -> services removed @@ -218,14 +218,14 @@ TEST_F(ServiceTrackingTest, AddRemoveServicesWithPropsAndOwnderTest) { } TEST_F(ServiceTrackingTest, UpdateTest) { - MarkerInterface1 intf1; - MarkerInterface2 intf2; - MarkerInterface3 intf3; + MarkerInterface1 intf1{}; + MarkerInterface2 intf2{}; + MarkerInterface3 intf3{}; - std::vector<MarkerInterface1*> services{}; + std::vector<std::shared_ptr<MarkerInterface1>> services{}; celix::ServiceTrackerOptions<MarkerInterface1> opts{}; - opts.update = [&services](std::vector<MarkerInterface1*> rankedServices) { + opts.update = [&services](std::vector<std::shared_ptr<MarkerInterface1>> rankedServices) { services = rankedServices; }; @@ -236,25 +236,25 @@ TEST_F(ServiceTrackingTest, UpdateTest) { auto trk1 = registry().trackServices(opts); EXPECT_EQ(1, trk1.trackCount()); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf1, services[0]); //should be intf1 + EXPECT_EQ(&intf1, services[0].get()); //should be intf1 reg1.unregister(); EXPECT_EQ(0, services.size()); reg1 = registry().registerService(intf1); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf1, services[0]); //should be intf1 again + EXPECT_EQ(&intf1, services[0].get()); //should be intf1 again MarkerInterface1 intf4{}; celix::Properties props{std::make_pair(celix::SERVICE_RANKING, "100")}; auto reg4 = registry().registerService(intf4, std::move(props)); ASSERT_EQ(2, services.size()); - EXPECT_EQ(&intf4, services[0]); //note 4 higher ranking - EXPECT_EQ(&intf1, services[1]); + EXPECT_EQ(&intf4, services[0].get()); //note 4 higher ranking + EXPECT_EQ(&intf1, services[1].get()); reg1.unregister(); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf4, services[0]); //intf1 gone -> index 0: intf4 + EXPECT_EQ(&intf4, services[0].get()); //intf1 gone -> index 0: intf4 trk1.stop(); EXPECT_EQ(0, services.size()); @@ -262,7 +262,7 @@ TEST_F(ServiceTrackingTest, UpdateTest) { { auto trk2 = registry().trackServices(opts); ASSERT_EQ(1, services.size()); - EXPECT_EQ(&intf4, services[0]); + EXPECT_EQ(&intf4, services[0].get()); //out of scope -> tracker stopped } EXPECT_EQ(0, services.size()); //stop tracking -> services removed diff --git a/libs/registry/include/celix/Constants.h b/libs/registry/include/celix/Constants.h index f1fcad3..517a372 100644 --- a/libs/registry/include/celix/Constants.h +++ b/libs/registry/include/celix/Constants.h @@ -24,9 +24,10 @@ namespace celix { //NOTE manually aligned with celix_constants.h - static constexpr const char *const SERVICE_NAME = "service.name"; - static constexpr const char *const SERVICE_ID = "service.id"; - static constexpr const char *const SERVICE_RANKING = "service.ranking"; + static constexpr const char *const SERVICE_NAME = "SERVICE_NAME"; + static constexpr const char *const SERVICE_ID = "SERVICE_ID"; + static constexpr const char *const SERVICE_RANKING = "SERVICE_RANKING"; + static constexpr const char *const SERVICE_BUNDLE = "SERVICE_BUNDLE"; static constexpr const char *const FRAMEWORK_UUID = "framework.uuid"; diff --git a/libs/registry/include/celix/Properties.h b/libs/registry/include/celix/Properties.h index 65fd4ef..1002bfc 100644 --- a/libs/registry/include/celix/Properties.h +++ b/libs/registry/include/celix/Properties.h @@ -55,6 +55,12 @@ namespace celix { std::string val = getProperty(props, key, std::to_string(defaultValue)); return std::stoul(val, nullptr, 10); } + + /*TODO + celix::Properties loadProperties(const std::string &path); + celix::Properties loadProperties(std::istream stream); + bool storeProperties(const celix::Properties &props, const std::string &path); + */ } #endif //CXX_CELIX_PROPERTIES_H diff --git a/libs/registry/include/celix/ServiceRegistry.h b/libs/registry/include/celix/ServiceRegistry.h index 7bbe16c..710b218 100644 --- a/libs/registry/include/celix/ServiceRegistry.h +++ b/libs/registry/include/celix/ServiceRegistry.h @@ -70,25 +70,23 @@ namespace celix { std::string filter{}; - /*TODO maybe refactor all I* to std::shared_ptr and use a custom deleter to sync whether a bundle is done using - all the functions -> i.e. safe delete and possible lock free? Not sure, because a std::shared_ptr instance - access it not thread safe?. Investigate */ + std::function<void(std::shared_ptr<I> svc)> set{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props)> setWithProperties{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props, const celix::IResourceBundle &owner)> setWithOwner{}; - std::function<void(I *svc)> set = {}; - std::function<void(I *svc, const celix::Properties &props)> setWithProperties = {}; - std::function<void(I *svc, const celix::Properties &props, const celix::IResourceBundle &owner)> setWithOwner = {}; + std::function<void(std::shared_ptr<I> svc)> add{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props)> addWithProperties{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props, const celix::IResourceBundle &owner)> addWithOwner{}; - std::function<void(I *svc)> add = {}; - std::function<void(I *svc, const celix::Properties &props)> addWithProperties = {}; - std::function<void(I *svc, const celix::Properties &props, const celix::IResourceBundle &owner)> addWithOwner = {}; + std::function<void(std::shared_ptr<I> svc)> remove{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props)> removeWithProperties{}; + std::function<void(std::shared_ptr<I> svc, const celix::Properties &props, const celix::IResourceBundle &owner)> removeWithOwner{}; - std::function<void(I *svc)> remove = {}; - std::function<void(I *svc, const celix::Properties &props)> removeWithProperties = {}; - std::function<void(I *svc, const celix::Properties &props, const celix::IResourceBundle &owner)> removeWithOwner = {}; + std::function<void(std::vector<std::shared_ptr<I>> rankedServices)> update{}; + std::function<void(std::vector<std::tuple<std::shared_ptr<I>, const celix::Properties*>> rankedServices)> updateWithProperties{}; + std::function<void(std::vector<std::tuple<std::shared_ptr<I>, const celix::Properties*, const celix::IResourceBundle *>> rankedServices)> updateWithOwner{}; - std::function<void(std::vector<I*> rankedServices)> update = {}; - std::function<void(std::vector<std::tuple<I*, const celix::Properties*>> rankedServices)> updateWithProperties = {}; - std::function<void(std::vector<std::tuple<I*, const celix::Properties*, const celix::IResourceBundle *>> rankedServices)> updateWithOwner = {}; + //TODO lock free update calls atomics, rcu, hazard pointers ?? }; //RAII service tracker: out of scope -> stop tracker @@ -107,7 +105,7 @@ namespace celix { const std::string& filter() const; bool valid() const; - //TODO useService(s) calls + //TODO use(Function)Service(s) calls void stop(); private: @@ -140,7 +138,8 @@ namespace celix { celix::ServiceRegistration registerService(std::shared_ptr<I> svc, celix::Properties props = {}, std::shared_ptr<const celix::IResourceBundle> owner = {}) { //TOOD refactor to using a service factory to store the shared or unique_ptr auto svcName = celix::serviceName<I>(); - return registerService(svcName, static_cast<std::shared_ptr<void>>(svc), std::move(props), std::move(owner)); + auto voidSvc = std::static_pointer_cast<I>(svc); + return registerService(svcName, voidSvc, std::move(props), std::move(owner)); } template<typename F> @@ -159,7 +158,7 @@ namespace celix { template<typename I> //NOTE C++17 typename std::enable_if<std::is_callable<I>::value, long>::type long findFunctionService(const std::string &functionName, const std::string &filter = "") const { - auto services = functionServiceName<I>(functionName, filter); + auto services = findFunctionService<I>(functionName, filter); return services.size() > 0 ? services[0] : -1L; } @@ -167,14 +166,14 @@ namespace celix { //NOTE C++17 typename std::enable_if<!std::is_callable<I>::value, std::vector<long>>::type std::vector<long> findServices(const std::string &filter = "") const { auto svcName = celix::serviceName<I>(); - return findServices(svcName, filter); + return findAnyServices(svcName, filter); } template<typename F> //NOTE C++17 typename std::enable_if<std::is_callable<I>::value, std::vector<long>>::type std::vector<long> findFunctionServices(const std::string &functionName, const std::string &filter = "") const { auto svcName = celix::functionServiceName<F>(functionName); - return findServices(svcName, filter); + return findAnyServices(svcName, filter); } template<typename I> @@ -264,6 +263,30 @@ namespace celix { } + //GENERIC / ANY calls. note these work on void + + int useAnyServices( + const std::string &svcName, + std::function<void(std::shared_ptr<void> svc, const celix::Properties &props,const celix::IResourceBundle &bnd)> use, + const std::string &filter = {}, + std::shared_ptr<const celix::IResourceBundle> requester = {}) const; + + bool useAnyService( + const std::string &svcName, + std::function<void(std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> use, + const std::string &filter = {}, + std::shared_ptr<const celix::IResourceBundle> requester = {}) const; + + std::vector<long> findAnyServices(const std::string &name, const std::string &filter = {}) const; + + + celix::ServiceTracker trackAnyServices( + std::string svcName, + ServiceTrackerOptions<void> options, + std::shared_ptr<const celix::IResourceBundle> requester = {}); + + //some aditional registry info + std::vector<std::string> listAllRegisteredServiceNames() const; long nrOfRegisteredServices() const; long nrOfServiceTrackers() const; private: @@ -283,7 +306,6 @@ namespace celix { std::function<void(I &svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> useWithOwner, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const; - int useServices(const std::string &svcName, std::function<void(void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> &use, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const; template<typename I> bool useService( @@ -293,16 +315,14 @@ namespace celix { std::function<void(I &svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> useWithOwner, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const; - bool useService(const std::string &svcName, std::function<void(void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> &use, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const; - - //find Services - std::vector<long> findServices(const std::string &name, const std::string &filter) const; - //track services template<typename I> - celix::ServiceTracker trackServices(std::string svcName, celix::ServiceTrackerOptions<I> options, std::shared_ptr<const celix::IResourceBundle> requester); - celix::ServiceTracker trackServices(std::string svcName, ServiceTrackerOptions<void> options, std::shared_ptr<const celix::IResourceBundle> requester); + celix::ServiceTracker trackServices( + std::string svcName, + celix::ServiceTrackerOptions<I> options, + std::shared_ptr<const celix::IResourceBundle> requester); + }; } @@ -360,8 +380,9 @@ inline int celix::ServiceRegistry::useServices( std::function<void(I &svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> useWithOwner, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const { - std::function<void(void*,const celix::Properties&, const celix::IResourceBundle&)> voidUse = [&](void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { - I* typedSvc = static_cast<I*>(svc); + + std::function<void(std::shared_ptr<void>, const celix::Properties&, const celix::IResourceBundle&)> voidUse = [&](std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { + std::shared_ptr<I> typedSvc = std::static_pointer_cast<I>(svc); if (use) { use(*typedSvc); } @@ -372,7 +393,7 @@ inline int celix::ServiceRegistry::useServices( useWithOwner(*typedSvc, props, bnd); } }; - return useServices(svcName, voidUse, filter, requester); + return useAnyServices(svcName, std::move(voidUse), filter, std::move(requester)); } template<typename I> @@ -383,8 +404,9 @@ inline bool celix::ServiceRegistry::useService( std::function<void(I &svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> useWithOwner, const std::string &filter, std::shared_ptr<const celix::IResourceBundle> requester) const { - std::function<void(void*,const celix::Properties&, const celix::IResourceBundle&)> voidUse = [&](void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd) -> void { - I* typedSvc = static_cast<I*>(svc); + + std::function<void(std::shared_ptr<void>,const celix::Properties&, const celix::IResourceBundle&)> voidUse = [&](std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &bnd) -> void { + std::shared_ptr<I> typedSvc = std::static_pointer_cast<I>(svc); if (use) { use(*typedSvc); } @@ -395,115 +417,120 @@ inline bool celix::ServiceRegistry::useService( useWithOwner(*typedSvc, props, bnd); } }; - return useService(svcName, voidUse, filter, requester); + return useAnyService(svcName, std::move(voidUse), filter, std::move(requester)); } template<typename I> -inline celix::ServiceTracker celix::ServiceRegistry::trackServices(std::string svcName, const celix::ServiceTrackerOptions<I> options, std::shared_ptr<const celix::IResourceBundle> requester) { +inline celix::ServiceTracker celix::ServiceRegistry::trackServices(std::string svcName, + ServiceTrackerOptions<I> options, + std::shared_ptr<const celix::IResourceBundle> requester) { ServiceTrackerOptions<void> opts{}; opts.filter = std::move(options.filter); if (options.set != nullptr) { auto set = std::move(options.set); - opts.set = [set](void *svc){ - I *typedSvc = static_cast<I*>(svc); + opts.set = [set](std::shared_ptr<void> svc){ + auto typedSvc = std::static_pointer_cast<I>(svc); set(typedSvc); }; } if (options.setWithProperties != nullptr) { auto set = std::move(options.setWithProperties); - opts.setWithProperties = [set](void *svc, const celix::Properties &props){ - I *typedSvc = static_cast<I*>(svc); + opts.setWithProperties = [set](std::shared_ptr<void> svc, const celix::Properties &props){ + auto typedSvc = std::static_pointer_cast<I>(svc); set(typedSvc, props); }; } if (options.setWithOwner != nullptr) { auto set = std::move(options.setWithOwner); - opts.setWithOwner = [set](void *svc, const celix::Properties &props, const celix::IResourceBundle &owner){ - I *typedSvc = static_cast<I*>(svc); + opts.setWithOwner = [set](std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &owner){ + auto typedSvc = std::static_pointer_cast<I>(svc); set(typedSvc, props, owner); }; } if (options.add != nullptr) { auto add = std::move(options.add); - opts.add = [add](void *svc) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.add = [add](std::shared_ptr<void> svc) { + auto typedSvc = std::static_pointer_cast<I>(svc); add(typedSvc); }; } if (options.addWithProperties != nullptr) { auto add = std::move(options.addWithProperties); - opts.addWithProperties = [add](void *svc, const celix::Properties &props) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.addWithProperties = [add](std::shared_ptr<void> svc, const celix::Properties &props) { + auto typedSvc = std::static_pointer_cast<I>(svc); add(typedSvc, props); }; } if (options.addWithOwner != nullptr) { auto add = std::move(options.addWithOwner); - opts.addWithOwner = [add](void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.addWithOwner = [add](std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { + auto typedSvc = std::static_pointer_cast<I>(svc); add(typedSvc, props, bnd); }; } if (options.remove != nullptr) { auto rem = std::move(options.remove); - opts.remove = [rem](void *svc) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.remove = [rem](std::shared_ptr<void> svc) { + auto typedSvc = std::static_pointer_cast<I>(svc); rem(typedSvc); }; } if (options.removeWithProperties != nullptr) { auto rem = std::move(options.removeWithProperties); - opts.removeWithProperties = [rem](void *svc, const celix::Properties &props) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.removeWithProperties = [rem](std::shared_ptr<void> svc, const celix::Properties &props) { + auto typedSvc = std::static_pointer_cast<I>(svc); rem(typedSvc, props); }; } if (options.removeWithOwner != nullptr) { auto rem = std::move(options.removeWithOwner); - opts.removeWithOwner = [rem](void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { - I *typedSvc = static_cast<I*>(svc); //note actual argument is I* + opts.removeWithOwner = [rem](std::shared_ptr<void> svc, const celix::Properties &props, const celix::IResourceBundle &bnd) { + auto typedSvc = std::static_pointer_cast<I>(svc); rem(typedSvc, props, bnd); }; } if (options.update != nullptr) { auto update = std::move(options.update); - opts.update = [update](std::vector<void*> rankedServices) { - std::vector<I*> typedServices{}; + opts.update = [update](std::vector<std::shared_ptr<void>> rankedServices) { + std::vector<std::shared_ptr<I>> typedServices{}; typedServices.reserve(rankedServices.size()); - for (void *svc : rankedServices) { - typedServices.push_back(static_cast<I*>(svc)); + for (auto &svc : rankedServices) { + auto typedSvc = std::static_pointer_cast<I>(svc); + typedServices.push_back(typedSvc); } update(std::move(typedServices)); }; } if (options.updateWithProperties != nullptr) { auto update = std::move(options.updateWithProperties); - opts.updateWithProperties = [update](std::vector<std::tuple<void*, const celix::Properties *>> rankedServices) { - std::vector<std::tuple<I*, const celix::Properties*>> typedServices{}; + opts.updateWithProperties = [update](std::vector<std::tuple<std::shared_ptr<void>, const celix::Properties *>> rankedServices) { + std::vector<std::tuple<std::shared_ptr<I>, const celix::Properties*>> typedServices{}; typedServices.reserve(rankedServices.size()); for (auto &tuple : rankedServices) { - typedServices.push_back(std::make_tuple(static_cast<I*>(std::get<0>(tuple)), std::get<1>(tuple))); + auto typedSvc = std::static_pointer_cast<I>(std::get<0>(tuple)); + typedServices.push_back(std::make_tuple(typedSvc, std::get<1>(tuple))); } update(std::move(typedServices)); }; } if (options.updateWithOwner != nullptr) { auto update = std::move(options.updateWithOwner); - opts.updateWithOwner = [update](std::vector<std::tuple<void*, const celix::Properties *, const celix::IResourceBundle*>> rankedServices) { - std::vector<std::tuple<I*, const celix::Properties*, const celix::IResourceBundle*>> typedServices{}; + opts.updateWithOwner = [update](std::vector<std::tuple<std::shared_ptr<void>, const celix::Properties *, const celix::IResourceBundle*>> rankedServices) { + std::vector<std::tuple<std::shared_ptr<I>, const celix::Properties*, const celix::IResourceBundle*>> typedServices{}; typedServices.reserve(rankedServices.size()); for (auto &tuple : rankedServices) { - typedServices.push_back(std::make_tuple(static_cast<I*>(std::get<0>(tuple)), std::get<1>(tuple), std::get<2>(tuple))); + auto typedSvc = std::static_pointer_cast<I>(std::get<0>(tuple)); + typedServices.push_back(std::make_tuple(typedSvc, std::get<1>(tuple), std::get<2>(tuple))); } update(std::move(typedServices)); }; } - return trackServices(std::move(svcName), std::move(opts), requester); + return trackAnyServices(std::move(svcName), std::move(opts), requester); } #endif //CXX_CELIX_SERVICEREGISTRY_H diff --git a/libs/registry/src/ServiceRegistry.cc b/libs/registry/src/ServiceRegistry.cc index 862013f..ac9a9dd 100644 --- a/libs/registry/src/ServiceRegistry.cc +++ b/libs/registry/src/ServiceRegistry.cc @@ -23,7 +23,7 @@ #include <mutex> #include <set> #include <utility> -#include <thread> +#include <future> #include <glog/logging.h> @@ -85,13 +85,12 @@ namespace { bool factory() const { return svcFactory != nullptr; } void incrUsage() const { - LOG(WARNING) << "TODO use shared_ptr unique instead ?? how to sync?"; + //TODO look at atomics or shared_ptr to handled to counts / sync std::lock_guard<std::mutex> lck{mutex}; usage += 1; } void decrUsage() const { - LOG(WARNING) << "TODO use shared_ptr unique instead ?? how is sync?"; std::lock_guard<std::mutex> lck{mutex}; usage -= 1; cond.notify_all(); @@ -140,17 +139,15 @@ namespace { ~SvcTrackerEntry() {} void clear() { - //TODO update, make special rem (e.g. only call the use set callbacks once with a nullptr) std::vector<std::shared_ptr<const SvcEntry>> removeEntries{}; { std::lock_guard<std::mutex> lck{tracked.mutex}; - for (auto &entry : tracked.entries) { - removeEntries.push_back(entry); + for (const auto &entry : tracked.entries) { + removeEntries.push_back(entry.first); } - tracked.entries.clear(); } for (auto &entry : removeEntries) { - remMatch(entry); //note fill try to erase entry from entries again, TODO check if this is safe + remMatch(entry); } } @@ -171,56 +168,74 @@ namespace { void addMatch(std::shared_ptr<const SvcEntry> entry) { //increase usage so that services cannot be removed while a service tracker is still active + + //new custom deleter which arranges the count & sync for the used services + entry->incrUsage(); + void *rawSvc = entry->service(*owner); + //NOTE creating a shared_ptr with a custom deleter, so that the SvcEntry usage is synced with this shared_ptr. + auto svc = std::shared_ptr<void>{rawSvc, [entry](void *) { + entry->decrUsage(); + }}; + { std::lock_guard<std::mutex> lck{tracked.mutex}; - tracked.entries.insert(entry); + tracked.entries.emplace(entry, svc); } //call callbacks callSetCallbacks(); - callAddRemoveCallbacks(entry, true); + callAddRemoveCallbacks(entry, svc, true); callUpdateCallbacks(); } void remMatch(const std::shared_ptr<const SvcEntry> &entry) { + std::shared_ptr<void> svc{}; { std::lock_guard<std::mutex> lck{tracked.mutex}; - tracked.entries.erase(entry); + auto it = tracked.entries.find(entry); + svc = it->second; + tracked.entries.erase(it); } //call callbacks - callSetCallbacks(); - callAddRemoveCallbacks(entry, false); + callSetCallbacks(); //note also removed highest if that was set to this svc + callAddRemoveCallbacks(entry, svc, false); callUpdateCallbacks(); - //decrease usage so that services cannot be removed while a service tracker is still active - entry->decrUsage(); + + //note sync will be done on the SvcEntry usage, which is controlled by the tracker svc shared ptr } - void callAddRemoveCallbacks(const std::shared_ptr<const SvcEntry> &updatedEntry, bool add) { + void callAddRemoveCallbacks(const std::shared_ptr<const SvcEntry> &entry, std::shared_ptr<void> &svc, bool add) { auto &update = add ? opts.add : opts.remove; - if (update != nullptr) { - void *svc = updatedEntry->service(*owner); + auto &updateWithProps = add ? opts.addWithProperties : opts.removeWithProperties; + auto &updateWithOwner = add ? opts.addWithOwner : opts.removeWithOwner; + if (update) { update(svc); } - //TODO rest of add/remove + if (updateWithProps) { + updateWithProps(svc, entry->props); + } + if (updateWithOwner) { + updateWithOwner(svc, entry->props, *entry->owner); + } } void callSetCallbacks() { - std::shared_ptr<const SvcEntry> currentHighest; + std::shared_ptr<void> currentHighestSvc{}; + std::shared_ptr<const SvcEntry> currentHighestSvcEntry{}; bool highestUpdated = false; { std::lock_guard<std::mutex> lck{tracked.mutex}; auto begin = tracked.entries.begin(); - if (begin == tracked.entries.end()) { - currentHighest = nullptr; - } else { - currentHighest = *begin; + if (begin != tracked.entries.end()) { + currentHighestSvc = begin->second; + currentHighestSvcEntry = begin->first; } - if (currentHighest != tracked.highest) { - tracked.highest = currentHighest; + if (currentHighestSvc != tracked.highest) { + tracked.highest = currentHighestSvc; highestUpdated = true; } } @@ -228,43 +243,58 @@ namespace { //TODO race condition. highest can be updated because lock is released. if (highestUpdated) { - void *svc = currentHighest == nullptr ? nullptr : currentHighest->service(*owner); - if (opts.set != nullptr) { - opts.set(svc); + if (opts.set) { + opts.set(currentHighestSvc); //note can be nullptr + } + if (opts.setWithProperties) { + opts.setWithProperties(currentHighestSvc, currentHighestSvcEntry->props); + } + if (opts.setWithOwner) { + opts.setWithOwner(currentHighestSvc, currentHighestSvcEntry->props, *currentHighestSvcEntry->owner); } - //TODO rest of set } } void callUpdateCallbacks() { + std::vector<std::tuple<std::shared_ptr<void>, const celix::Properties*, const celix::IResourceBundle*>> rankedServices{}; + if (opts.update || opts.updateWithProperties || opts.updateWithOwner) { + //fill vector + std::lock_guard<std::mutex> lck{tracked.mutex}; + rankedServices.reserve(tracked.entries.size()); + for (auto &tracked : tracked.entries) { + rankedServices.push_back(std::make_tuple(tracked.second, &tracked.first->props, tracked.first->owner.get())); + } + } if (opts.update) { - std::vector<void *> rankedServices{}; - { - std::lock_guard<std::mutex> lck{tracked.mutex}; - rankedServices.reserve(tracked.entries.size()); - for (auto &tracked : tracked.entries) { - rankedServices.push_back(tracked->service(*owner)); - } + std::vector<std::shared_ptr<void>> rnk{}; + for (auto &tuple : rankedServices) { + rnk.push_back(std::get<0>(tuple)); + } + opts.update(std::move(rnk)); + } + if (opts.updateWithProperties) { + std::vector<std::tuple<std::shared_ptr<void>, const celix::Properties*>> rnk{}; + for (auto &tuple : rankedServices) { + rnk.push_back(std::make_pair(std::get<0>(tuple), std::get<1>(tuple))); } - opts.update(std::move(rankedServices)); + opts.updateWithProperties(std::move(rnk)); + } + if (opts.updateWithOwner) { + opts.updateWithOwner(std::move(rankedServices)); } - //TODO rest of the update calls } int count() const { - LOG(INFO) << "TODO use shared_ptr count instead"; std::lock_guard<std::mutex> lck{tracked.mutex}; return (int)tracked.entries.size(); } void incrUsage() const { - LOG(INFO) << "TODO use shared_ptr count instead"; std::lock_guard<std::mutex> lck{mutex}; usage += 1; } void decrUsage() const { - LOG(INFO) << "TODO use shared_ptr count instead"; std::lock_guard<std::mutex> lck{mutex}; usage -= 1; cond.notify_all(); @@ -277,12 +307,11 @@ namespace { private: struct { mutable std::mutex mutex; //protects matchedEntries & highestRanking - std::set<std::shared_ptr<const SvcEntry>, SvcEntryLess> entries{}; - std::shared_ptr<const SvcEntry> highest{}; + std::map<std::shared_ptr<const SvcEntry>, std::shared_ptr<void>, SvcEntryLess> entries{}; + std::shared_ptr<void> highest{}; } tracked{}; - //sync TODO refactor to atomics mutable std::mutex mutex{}; mutable std::condition_variable cond{}; mutable int usage{1}; @@ -354,6 +383,7 @@ public: //Add to registry std::shared_ptr<const celix::IResourceBundle> bnd = owner ? owner : emptyBundle; + props[celix::SERVICE_BUNDLE] = std::to_string(bnd->id()); if (factory) { VLOG(1) << "Registering service factory '" << svcName << "' from bundle id " << owner->id() << std::endl; @@ -369,8 +399,8 @@ public: services.cache[entry->svcId] = entry; //update trackers - std::thread updateThread{[&]{updateTrackers(entry, true);}}; - updateThread.join(); + auto future = std::async([&]{updateTrackers(entry, true);}); + future.wait(); entry->decrUsage(); //note usage started at 1 during creation @@ -395,17 +425,22 @@ public: { std::lock_guard<std::mutex> lock{services.mutex}; - const auto it = services.cache.find(svcId); - if (it != services.cache.end()) { - match = it->second; - services.cache.erase(it); - services.registry.at(match->svcName).erase(match); + const auto cacheIter = services.cache.find(svcId); + if (cacheIter != services.cache.end()) { + match = cacheIter->second; + services.cache.erase(cacheIter); + const auto svcSetIter = services.registry.find(match->svcName); + svcSetIter->second.erase(match); + if (svcSetIter->second.empty()) { + //last entry in the registry for this service name. + services.registry.erase(svcSetIter); + } } } if (match) { - std::thread updateThread{[&]{updateTrackers(match, false);}}; - updateThread.join(); + auto future = std::async([&]{updateTrackers(match, false);}); + future.wait(); match->waitTillUnused(); } else { LOG(WARNING) << "Cannot unregister service. Unknown service id: " << svcId << "." << std::endl; @@ -430,8 +465,8 @@ public: if (match) { match->waitTillUnused(); - std::thread clearThread{[&]{match->clear();}}; //ensure that all service are removed using the callbacks - clearThread.join(); + auto future = std::async([&]{match->clear();}); //ensure that all service are removed using the callbacks + future.wait(); } else { LOG(WARNING) << "Cannot remove tracker. Unknown tracker id: " << trkId << "." << std::endl; } @@ -458,6 +493,15 @@ public: match->decrUsage(); } } + + std::vector<std::string> listAllRegisteredServiceNames() const { + std::vector<std::string> result{}; + std::lock_guard<std::mutex> lck{services.mutex}; + for (const auto& pair : services.registry) { + result.emplace_back(std::string{pair.first}); + } + return result; + } }; @@ -496,7 +540,9 @@ celix::ServiceRegistration celix::ServiceRegistry::registerServiceFactory(std::s //TODO add useService(s) call to ServiceTracker object for fast service access //TODO move to Impl -celix::ServiceTracker celix::ServiceRegistry::trackServices(std::string svcName, celix::ServiceTrackerOptions<void> options, std::shared_ptr<const celix::IResourceBundle> requester) { +celix::ServiceTracker celix::ServiceRegistry::trackAnyServices(std::string svcName, + celix::ServiceTrackerOptions<void> options, + std::shared_ptr<const celix::IResourceBundle> requester) { //TODO create new tracker event and start new thread to update track trackers long trkId = 0; { @@ -523,13 +569,13 @@ celix::ServiceTracker celix::ServiceRegistry::trackServices(std::string svcName, pimpl->trackers.registry[trkEntry->svcName].insert(trkEntry); pimpl->trackers.cache[trkEntry->id] = trkEntry; } - std::thread updateThread{[&]{ + auto future = std::async([&]{ for (auto &svcEntry : services) { trkEntry->addMatch(svcEntry); svcEntry->decrUsage(); } - }}; - updateThread.join(); + }); + future.wait(); trkEntry->decrUsage(); //note trkEntry usage started at 1 auto untrack = [this, trkId]() -> void { @@ -556,16 +602,16 @@ long celix::ServiceRegistry::nrOfServiceTrackers() const { //TODO unregister tracker with remove tracker event in a new thread //TODO move to Impl -std::vector<long> celix::ServiceRegistry::findServices(const std::string &svcName, const std::string &rawFilter) const { +std::vector<long> celix::ServiceRegistry::findAnyServices(const std::string &name, const std::string &f) const { std::vector<long> result{}; - celix::Filter filter = rawFilter; + celix::Filter filter = f; if (!filter.valid()) { - LOG(WARNING) << "Invalid filter (" << rawFilter << ") provided. Cannot find services" << std::endl; + LOG(WARNING) << "Invalid filter (" << f << ") provided. Cannot find services" << std::endl; return result; } std::lock_guard<std::mutex> lock{pimpl->services.mutex}; - const auto it = pimpl->services.registry.find(svcName); + const auto it = pimpl->services.registry.find(name); if (it != pimpl->services.registry.end()) { const auto &services = it->second; for (const auto &visit : services) { @@ -584,10 +630,14 @@ long celix::ServiceRegistry::nrOfRegisteredServices() const { } //TODO move to Impl -int celix::ServiceRegistry::useServices(const std::string &svcName, std::function<void(void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> &use, const std::string &rawFilter, std::shared_ptr<const celix::IResourceBundle> requester) const { - celix::Filter filter = rawFilter; +int celix::ServiceRegistry::useAnyServices(const std::string &svcName, + std::function<void(std::shared_ptr<void> svc, const celix::Properties &props, + const celix::IResourceBundle &bnd)> use, + const std::string &f, + std::shared_ptr<const celix::IResourceBundle> requester) const { + celix::Filter filter = f; if (!filter.valid()) { - LOG(WARNING) << "Invalid filter (" << rawFilter << ") provided. Cannot find services" << std::endl; + LOG(WARNING) << "Invalid filter (" << f << ") provided. Cannot find services" << std::endl; return 0; } @@ -607,7 +657,11 @@ int celix::ServiceRegistry::useServices(const std::string &svcName, std::functio } for (const std::shared_ptr<const SvcEntry> &entry : matches) { - use(entry->service(*requester), entry->props, *entry->owner); + void *rawSvc = entry->service(*requester); + std::shared_ptr<void> svc{rawSvc, [entry](void *) { + entry->decrUsage(); + }}; + use(svc, entry->props, *entry->owner); entry->decrUsage(); } @@ -615,10 +669,14 @@ int celix::ServiceRegistry::useServices(const std::string &svcName, std::functio } //TODO move to Impl -bool celix::ServiceRegistry::useService(const std::string &svcName, std::function<void(void *svc, const celix::Properties &props, const celix::IResourceBundle &bnd)> &use, const std::string &rawFilter, std::shared_ptr<const celix::IResourceBundle> requester) const { - celix::Filter filter = rawFilter; +bool celix::ServiceRegistry::useAnyService(const std::string &svcName, + std::function<void(std::shared_ptr<void> svc, const celix::Properties &props, + const celix::IResourceBundle &bnd)> use, + const std::string &f, + std::shared_ptr<const celix::IResourceBundle> requester) const { + celix::Filter filter = f; if (!filter.valid()) { - LOG(WARNING) << "Invalid filter (" << rawFilter << ") provided. Cannot find services" << std::endl; + LOG(WARNING) << "Invalid filter (" << f << ") provided. Cannot find services" << std::endl; return false; } @@ -639,13 +697,20 @@ bool celix::ServiceRegistry::useService(const std::string &svcName, std::functio } if (match != nullptr) { - use(match->service(*requester), match->props, *match->owner); - match->decrUsage(); + void *rawSvc = match->service(*requester); + std::shared_ptr<void> svc{rawSvc, [match](void *) { + match->decrUsage(); + }}; + use(svc, match->props, *match->owner); } return match != nullptr; } +std::vector<std::string> celix::ServiceRegistry::listAllRegisteredServiceNames() const { + return pimpl->listAllRegisteredServiceNames(); +} + /********************************************************************************************************************** Service Registration **********************************************************************************************************************/
