@@ -14,9 +14,20 @@ | |||
#include <string> | |||
#include <set> | |||
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<std::string> 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<cppcms::application *(cppcms::service &)> cb = cppcms::plugin::manager::entry<cppcms::application *(cppcms::service &)>("foo","application"); | |||
/// booster::callback<cppcms::application *(cppcms::service &)> cb = :manager::instance().entry<cppcms::application *(cppcms::service &)>("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<cppcms::application *(cppcms::service &)>("myapi","app::generator")(service()); | |||
/// cppcms::application *app = manager::instance().entry<cppcms::application *(cppcms::service &)>("myapi","app::generator")(service()); | |||
/// attach(app,"/plugins/foo(/.*)",1); | |||
/// \endcode | |||
/// | |||
/// \ver{v1_2} | |||
template<typename Signature> | |||
booster::callback<Signature> | |||
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<type> | |||
/// \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<booster::refcounted> 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<type> | |||
/// | |||
/// 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<plugin_api *(std::string const &)>("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<type> | |||
/// | |||
/// 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<plugin_api *(std::string const &)>("myplugin","api")` | |||
#define CPPCMS_NAMED_PLUGIN_ENTRY(name,entry,call,type) CPPCMS_FULL_PLUGIN_ENTRY(#name,#entry,name :: call,type,#type) | |||
} // plugin | |||
@@ -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 | |||
@@ -10,6 +10,7 @@ | |||
#include <cppcms/json.h> | |||
#include <cppcms/localization.h> | |||
#include <cppcms/plugin.h> | |||
#include <booster/aio/io_service.h> | |||
#include <booster/aio/stream_socket.h> | |||
#include <booster/shared_ptr.h> | |||
@@ -67,6 +68,8 @@ namespace impl { | |||
std::auto_ptr<booster::aio::stream_socket> sig_,breaker_; | |||
std::vector<std::string> args_; | |||
booster::hold_ptr<plugin::scope> plugins_; | |||
}; | |||
@@ -7,15 +7,23 @@ | |||
/////////////////////////////////////////////////////////////////////////////// | |||
#define CPPCMS_SOURCE | |||
#include <cppcms/plugin.h> | |||
#include <cppcms/json.h> | |||
#include <cppcms/service.h> | |||
#include <map> | |||
#include <booster/locale/format.h> | |||
#include <booster/thread.h> | |||
#include <booster/shared_ptr.h> | |||
#include <booster/shared_object.h> | |||
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<std::string> modules; | |||
}; | |||
scope::_class_data &scope::class_data() | |||
{ | |||
static _class_data d; | |||
return d; | |||
} | |||
struct scope::_data { | |||
std::vector<std::string> paths; | |||
std::string pattern; | |||
std::map<std::string,booster::shared_ptr<booster::shared_object> > 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<booster::mutex> guard(cls.lock); | |||
for(std::map<std::string,booster::shared_ptr<booster::shared_object> >::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<std::string>()); | |||
d->pattern = v.get("plugin.shared_object_pattern",std::string()); | |||
std::vector<std::string> modules = v.get("plugin.modules",std::vector<std::string>()); | |||
for(size_t i=0;i<modules.size();i++) { | |||
load(modules[i]); | |||
} | |||
} | |||
bool scope::is_loaded(std::string const &name) | |||
{ | |||
_class_data &cls = class_data(); | |||
booster::unique_lock<booster::mutex> guard(cls.lock); | |||
return cls.modules.find(name)!=cls.modules.end(); | |||
} | |||
void scope::paths(std::vector<std::string> 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<booster::mutex> 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<booster::shared_object> 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;i<d->paths.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;i<d->paths.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<std::string,booster::shared_ptr<booster::shared_object> >::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 |
@@ -32,6 +32,7 @@ | |||
#include <cppcms/mount_point.h> | |||
#include <cppcms/forwarder.h> | |||
#include <cppcms/mem_bind.h> | |||
#include <cppcms/plugin.h> | |||
#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 |
@@ -1,4 +1,6 @@ | |||
#include <cppcms/plugin.h> | |||
#include <cppcms/service.h> | |||
#include <cppcms/json.h> | |||
#include <booster/shared_object.h> | |||
#include <iostream> | |||
#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<std::string(std::string const &)> cb; | |||
cb = manager::instance().entry<std::string(std::string const &)>("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<std::string> names = manager::instance().entries("foo"); | |||
std::set<std::string>::iterator p=names.begin(); | |||
TEST(*p++ == "bar::create"); | |||
TEST(*p++ == "counter"); | |||
TEST(*p++ == "lower"); | |||
TEST(p==names.end()); | |||
TEST(manager::instance().entry<int()>("foo","counter")()==1); | |||
TEST(manager::instance().entry<int()>("foo","counter")()==2); | |||
} | |||
obj.close(); | |||
std::cout << "- Unload" << std::endl; | |||
TEST(!cppcms::plugin::scope::is_loaded("plugin")); | |||
try { | |||
manager::instance().entry<std::string(std::string const &)>("foo","lower"); | |||
std::cerr << "Must Not get there:" << __LINE__<<std::endl; | |||
@@ -51,6 +67,37 @@ int main(int argc,char **argv) | |||
catch(cppcms::cppcms_error const &) {} | |||
catch(...) { throw std::runtime_error("Something else thrown"); } | |||
TEST(cppcms::plugin::manager::instance().has_plugin("foo")==false); | |||
std::cout << "- Scope vs Service" << std::endl; | |||
{ | |||
cppcms::service srv(params); | |||
TEST(manager::instance().has_plugin("foo")); | |||
TEST(manager::instance().entry<int()>("foo","counter")()==1); | |||
TEST(manager::instance().entry<int()>("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<int()>("foo","counter")()==3); | |||
} | |||
TEST(manager::instance().entry<int()>("foo","counter")()==4); | |||
} | |||
{ | |||
cppcms::plugin::scope sc(params); | |||
TEST(manager::instance().has_plugin("foo")); | |||
TEST(manager::instance().entry<int()>("foo","counter")()==1); | |||
TEST(manager::instance().entry<int()>("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<int()>("foo","counter")()==3); | |||
} | |||
TEST(manager::instance().entry<int()>("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; | |||
@@ -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<f.size();i++) { | |||
f[i]=tolower(f[i]); | |||
} | |||
return f; | |||
} | |||
CPPCMS_PLUGIN_ENTRY(foo,lower,std::string(std::string const &)) | |||
class bar : public bar_base { | |||
public: | |||
bar(std::string const &m) : msg_(m) {} | |||
virtual char const *msg() { return msg_.c_str(); }; | |||
static bar *create(std::string const &m) | |||
{ | |||
for(size_t i=0;i<f.size();i++) { | |||
f[i]=tolower(f[i]); | |||
} | |||
return f; | |||
return new bar(m); | |||
} | |||
class bar : public bar_base { | |||
public: | |||
bar(std::string const &m) : msg_(m) {} | |||
virtual char const *msg() { return msg_.c_str(); }; | |||
static bar *create(std::string const &m) | |||
{ | |||
return new bar(m); | |||
} | |||
private: | |||
std::string msg_; | |||
}; | |||
private: | |||
std::string msg_; | |||
}; | |||
CPPCMS_PLUGIN_ENTRY(foo,lower,std::string(std::string const &)) | |||
CPPCMS_PLUGIN_ENTRY(foo,bar::create,bar_base *(std::string const &)) | |||
int my_counter() | |||
{ | |||
static int value; | |||
return ++value; | |||
} | |||
CPPCMS_NAMED_PLUGIN_ENTRY(foo,counter,my_counter,int()); | |||
} // namespace foo | |||