diff --git a/cppcms/plugin.h b/cppcms/plugin.h index a3859da..a0086d8 100644 --- a/cppcms/plugin.h +++ b/cppcms/plugin.h @@ -14,9 +14,20 @@ #include #include +namespace booster { class shared_object; } + namespace cppcms { +namespace json { class value; } +/// +/// \brief Plugin related API +/// +/// \ver{v1_2} namespace plugin { +/// +/// An exception that is thrown in case of actual function signature is not matching the requested one +/// +/// \ver{v1_2} class CPPCMS_API signature_error : public booster::bad_cast { public: signature_error(std::string const &msg); @@ -26,6 +37,98 @@ private: std::string msg_; }; + +/// +/// Class that esures that plugin is loaded and unloads it in destructor if needed +/// +/// Note: it tracks the loaded plugins by its name globally such that if another scope had loaded the plugin +/// it wouldn't be loaded again. +/// +/// It is useable when plugin should be used outside of life scope of cppcms::service +/// +/// CppCMS configuration: +/// +/// - The search paths defined as array of strings in `plugin.paths` (optional) +/// - List of modules defined as array of strings in `plugin.modules` (optional, if you want to call load later) +/// - Shared object pattern defined as string in `plugin.shared_object_pattern` (optional) +/// +/// \ver{v1_2} +class CPPCMS_API scope { + scope(scope const &); + void operator=(scope const &); +public: + /// + /// Create an empty scope + /// + scope(); + /// + /// Unloads all loaded plugins + /// + ~scope(); + + /// + /// Loads the plugins provided in main cppcms configuration file - argc,argv are same parameters as for cppcms::service constructor + /// + scope(int argc,char **argv); + /// + /// Loads the plugins provided in main cppcms configuration json file - same parameters as for cppcms::service constructor + /// + scope(json::value const &value); + + /// + /// Set search path for plugins if undefined search according to the OS rules, if one of the paths in the vector is empty the search is performed by + //// OS search rules + /// + void paths(std::vector const &paths); + /// + /// Specify shared object/DLL naming convension. For example `lib{1}.dll` or `lib{1}.so` for converting the module name to shared object/dll name. + /// + /// Thus in the shared object \a pattern is `lib{1}.dll` that when module "foo" is loaded it tries to load `libfoo.dll` If not speficied default + /// nameing is used, see booster::shared_object::name + /// + void shared_object_pattern(std::string const &pattern); + + /// + /// Load specific module according to the paths and shared_object_pattern provided. Also note paths and pattern can be defined in cppcms configuration + /// in the constructor + /// + /// \note module name isn't nessary same as plugin name. Module refers to name of shared object or dll while plugin is application defined. Same dll/so can + /// contain multiple plugins or none. + /// + void load(std::string const &module); + + /// + /// Check if the module was loaded withing any of the scopes - note it is static member function + /// + static bool is_loaded(std::string const &module); + + /// + /// Get shared object loading withing \a this scope. If it wasn't loaded withing this scope throws cppcms_error + /// + booster::shared_object const &get(std::string const &module) const; + + /// + /// Check if module is loaded withing this scope, unlike is_loaded that checks for the module globally, it refers to this scope only + /// + bool is_loaded_by_this_scope(std::string const &module) const; +private: + void init(json::value const &config); + struct _class_data; + static _class_data &class_data(); + + struct _data; + booster::hold_ptr<_data> d; +}; + + +/// +/// Central class that manages registration of plugins. +/// +/// It is used as singleton and accessed via manager::instance(). +/// +/// Each plugin registers itself in the constructor and destructor implemented in shared library. +/// +/// class CPPCMS_API manager { public: /// @@ -47,7 +150,7 @@ public: /// /// For example /// \code - /// booster::callback cb = cppcms::plugin::manager::entry("foo","application"); + /// booster::callback cb = :manager::instance().entry("foo","application"); /// cppcms::application *app =cb(service()); /// attach(app,"/plugins/foo(/.*)",1); // attach new application /// \endcode @@ -55,11 +158,10 @@ public: /// Or /// /// \code - /// cppcms::application *app = cppcms::plugin::manager::entry("myapi","app::generator")(service()); + /// cppcms::application *app = manager::instance().entry("myapi","app::generator")(service()); /// attach(app,"/plugins/foo(/.*)",1); /// \endcode /// - /// \ver{v1_2} template booster::callback entry(std::string const &plugin_name,std::string const &entry_name) @@ -143,7 +245,13 @@ private: #define CPPCMS_PLUGIN_CONCAT(x,y) x ## y #define CPPCMS_PLUGIN_CONCAT2(x,y) CPPCMS_PLUGIN_CONCAT(x,y) -#define CPPCMS_NAMED_PLUGIN_ENTRY(plugin_name,call_name,call,type,signature) \ + +/// +/// Install generic plugin entry in plugin named \a plugin_name, the entry name \a call_name +/// and such that the &call represents valid assignment for booster::callback +/// \a signature is textual representation of the type used for error reporting +/// +#define CPPCMS_FULL_PLUGIN_ENTRY(plugin_name,call_name,call,type,signature) \ namespace { \ struct CPPCMS_PLUGIN_CONCAT2(stpg_ , __LINE__) { \ static booster::intrusive_ptr entry() \ @@ -166,7 +274,48 @@ namespace { \ } CPPCMS_PLUGIN_CONCAT2(instance_of_stpg_,__LINE__); \ } -#define CPPCMS_PLUGIN_ENTRY(name,call,type) CPPCMS_NAMED_PLUGIN_ENTRY(#name,#call,name :: call,type,#type) + +/// +/// Install common function entry such that \a name is plugin name, \a call is entry name and &name::call is valid assignment +/// for booster::callback +/// +/// Usually name should be namespace or class name, call is function or static member functions +/// +/// For example +/// \code +/// namespace myplugin { +/// class my_class : public plugin_api { +/// public: +/// statuc my_class *create(std::string const ¶meter) { return new my_class(parameter); } +/// ... +/// }; +/// CPPCMS_PLUGIN_ENTRY(myplugin,my_class::create,plugin_api *(std::string const &)) +/// } +/// \endcode +/// +/// it is accessed as `manager::instance().entry("myplugin","my_class::create")` +#define CPPCMS_PLUGIN_ENTRY(name,call,type) CPPCMS_FULL_PLUGIN_ENTRY(#name,#call,name :: call,type,#type) + +/// +/// Install common function entry such that \a name is plugin name, \a entry is entry name and &name::call is valid assignment +/// for booster::callback +/// +/// Usually name should be namespace or class name, call is function or static member functions +/// +/// For example +/// \code +/// namespace myplugin { +/// class my_class : public plugin_api { +/// public: +/// statuc my_class *create(std::string const ¶meter) { return new my_class(parameter); } +/// ... +/// }; +/// CPPCMS_NAMED_PLUGIN_ENTRY(myplugin,api,my_class::create,plugin_api *(std::string const &)) +/// } +/// \endcode +/// +/// it is accessed as `manager::instance().entry("myplugin","api")` +#define CPPCMS_NAMED_PLUGIN_ENTRY(name,entry,call,type) CPPCMS_FULL_PLUGIN_ENTRY(#name,#entry,name :: call,type,#type) } // plugin diff --git a/cppcms/service.h b/cppcms/service.h index 2b7ab3e..b8e68c5 100644 --- a/cppcms/service.h +++ b/cppcms/service.h @@ -27,6 +27,9 @@ namespace booster { /// \brief This is the namespace where all CppCMS functionality is placed /// namespace cppcms { + namespace plugin { + class scope; + } namespace impl { struct cached_settings; class service; @@ -185,6 +188,12 @@ namespace cppcms { /// int process_id(); + + /// + /// Get plugin scope ownd by the cppcms::service + /// + plugin::scope &plugins(); + /// \cond INTERNAL // internal functions never call it directly diff --git a/private/service_impl.h b/private/service_impl.h index 5436184..1c6bf0c 100644 --- a/private/service_impl.h +++ b/private/service_impl.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -67,6 +68,8 @@ namespace impl { std::auto_ptr sig_,breaker_; std::vector args_; + + booster::hold_ptr plugins_; }; diff --git a/src/plugin.cpp b/src/plugin.cpp index c206143..b4e7bcd 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -7,15 +7,23 @@ /////////////////////////////////////////////////////////////////////////////// #define CPPCMS_SOURCE #include +#include +#include #include +#include #include +#include +#include namespace cppcms { namespace plugin { namespace { struct init { - init() { manager::instance(); } + init() { + manager::instance(); + scope::is_loaded(""); + } } init_inst; } @@ -132,5 +140,136 @@ char const *signature_error::what() const throw() return msg_.c_str(); } + +struct scope::_class_data { + booster::mutex lock; + std::set modules; +}; + +scope::_class_data &scope::class_data() +{ + static _class_data d; + return d; +} + +struct scope::_data { + std::vector paths; + std::string pattern; + std::map > objects; +}; + +scope::scope() : d(new scope::_data()) +{ +} + +scope::scope(json::value const &v) : d(new scope::_data()) +{ + init(v); +} + +scope::~scope() +{ + try { + _class_data &cls = class_data(); + booster::unique_lock guard(cls.lock); + for(std::map >::iterator p=d->objects.begin();p!=d->objects.end();++p) { + cls.modules.erase(p->first); + } + d->objects.clear(); + } + catch(...) {} +} + +scope::scope(int argc,char **argv) : d(new scope::_data()) +{ + json::value v = service::load_settings(argc,argv); + init(v); +} + +void scope::init(json::value const &v) +{ + d->paths = v.get("plugin.paths",std::vector()); + d->pattern = v.get("plugin.shared_object_pattern",std::string()); + + std::vector modules = v.get("plugin.modules",std::vector()); + for(size_t i=0;i guard(cls.lock); + return cls.modules.find(name)!=cls.modules.end(); +} + +void scope::paths(std::vector const &paths) +{ + d->paths = paths; +} +void scope::shared_object_pattern(std::string const &p) +{ + d->pattern = p; +} + +void scope::load(std::string const &name) +{ + _class_data &cls = class_data(); + booster::unique_lock guard(cls.lock); + if(cls.modules.find(name)!=cls.modules.end()) + return; + std::string so_name; + if(d->pattern.empty()) + so_name = booster::shared_object::name(name); + else + so_name = (booster::locale::format(d->pattern) % name).str(std::locale::classic()); + + booster::shared_ptr obj(new booster::shared_object()); + if(d->paths.empty()) { + if(!obj->open(so_name)) + throw cppcms_error("Failed to load " + so_name); + } + else { + for(size_t i=0;ipaths.size();i++) { + std::string path = d->paths[i]; + if(path.empty()) + path = so_name; + else + path = path + "/" + so_name; + if(obj->open(so_name)) + break; + } + if(!obj->is_open()) { + std::ostringstream ss; + ss << "Failed to load " << so_name << " from "; + for(size_t i=0;ipaths.size();i++) { + if(i!=0) { + ss << ", "; + } + ss << "`" << d->paths[i] << "'"; + } + throw cppcms_error(ss.str()); + } + } + d->objects[name]=obj; + cls.modules.insert(name); +} + +bool scope::is_loaded_by_this_scope(std::string const &module) const +{ + return d->objects.find(module)!=d->objects.end(); +} + +booster::shared_object const &scope::get(std::string const &module) const +{ + std::map >::const_iterator p = d->objects.find(module); + if(p==d->objects.end()) + throw cppcms_error("Module `" + module + "' wasn't loaded withing this scope"); + return *p->second; +} + + } // plugin } // cppcms diff --git a/src/service.cpp b/src/service.cpp index d3b0ec3..486c2bd 100644 --- a/src/service.cpp +++ b/src/service.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "cgi_acceptor.h" #ifndef CPPCMS_WIN32 #include "prefork_acceptor.h" @@ -225,6 +226,7 @@ void service::setup() impl_->sig_.reset(new io::stream_socket(*impl_->io_service_)); impl_->breaker_.reset(new io::stream_socket(*impl_->io_service_)); + impl_->plugins_.reset(new cppcms::plugin::scope(settings())); impl_->applications_pool_.reset(new cppcms::applications_pool(*this,0)); impl_->views_pool_.reset(new cppcms::views::manager(settings())); impl_->cache_pool_.reset(new cppcms::cache_pool(settings())); @@ -997,10 +999,15 @@ namespace impl { // io_service, because soma apps may try unregister themselfs applications_pool_.reset(); locale_generator_.reset(); - settings_.reset(); - +settings_.reset(); + } } // impl +plugin::scope &service::plugins() +{ + return *impl_->plugins_; +} + } // cppcms diff --git a/tests/plugin_test.cpp b/tests/plugin_test.cpp index be1214d..2525364 100644 --- a/tests/plugin_test.cpp +++ b/tests/plugin_test.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include "test.h" @@ -12,10 +14,18 @@ int main(int argc,char **argv) return 1; } std::string path = argv[1]; + cppcms::json::value params; + + params["plugin"]["paths"][0]=path; + params["plugin"]["modules"][0]="plugin"; + try { using cppcms::plugin::manager; - booster::shared_object obj(path + "/" + booster::shared_object::name("plugin")); { + cppcms::plugin::scope sc(params); + TEST(sc.is_loaded_by_this_scope("plugin")); + TEST(cppcms::plugin::scope::is_loaded("plugin")); + std::cout << "- Normal call" << std::endl; booster::callback cb; cb = manager::instance().entry("foo","lower"); @@ -37,12 +47,18 @@ int main(int argc,char **argv) TEST(manager::instance().has_plugin("foo")); TEST(manager::instance().plugins().size()==1); TEST(*manager::instance().plugins().begin()=="foo"); - TEST(manager::instance().entries("foo").size()==2); - TEST(*manager::instance().entries("foo").begin()=="bar::create"); - TEST(*manager::instance().entries("foo").rbegin()=="lower"); + TEST(manager::instance().entries("foo").size()==3); + std::set names = manager::instance().entries("foo"); + std::set::iterator p=names.begin(); + TEST(*p++ == "bar::create"); + TEST(*p++ == "counter"); + TEST(*p++ == "lower"); + TEST(p==names.end()); + TEST(manager::instance().entry("foo","counter")()==1); + TEST(manager::instance().entry("foo","counter")()==2); } - obj.close(); std::cout << "- Unload" << std::endl; + TEST(!cppcms::plugin::scope::is_loaded("plugin")); try { manager::instance().entry("foo","lower"); std::cerr << "Must Not get there:" << __LINE__<("foo","counter")()==1); + TEST(manager::instance().entry("foo","counter")()==2); + TEST(srv.plugins().is_loaded_by_this_scope("plugin")); + TEST(cppcms::plugin::scope::is_loaded("plugin")); + { + cppcms::plugin::scope sc(params); + TEST(!sc.is_loaded_by_this_scope("plugin")); + TEST(manager::instance().entry("foo","counter")()==3); + } + TEST(manager::instance().entry("foo","counter")()==4); + } + { + cppcms::plugin::scope sc(params); + TEST(manager::instance().has_plugin("foo")); + TEST(manager::instance().entry("foo","counter")()==1); + TEST(manager::instance().entry("foo","counter")()==2); + TEST(cppcms::plugin::scope::is_loaded("plugin")); + { + cppcms::service srv(params); + TEST(!srv.plugins().is_loaded_by_this_scope("plugin")); + TEST(sc.is_loaded_by_this_scope("plugin")); + TEST(manager::instance().entry("foo","counter")()==3); + } + TEST(manager::instance().entry("foo","counter")()==4); + } + TEST(!manager::instance().has_plugin("foo")); + TEST(!cppcms::plugin::scope::is_loaded("plugin")); } catch(std::exception const &e) { std::cerr << "Error:" << e.what() << std::endl; diff --git a/tests/test_plugin_so.cpp b/tests/test_plugin_so.cpp index 022e7c6..8e11e5d 100644 --- a/tests/test_plugin_so.cpp +++ b/tests/test_plugin_so.cpp @@ -3,26 +3,38 @@ #include "plugin_base.h" namespace foo { - std::string lower(std::string f) + +std::string lower(std::string f) +{ + for(size_t i=0;i