Browse Source

Improved plugin API

master
Artyom Beilis 8 years ago
parent
commit
6f8478d79a
7 changed files with 398 additions and 32 deletions
  1. +154
    -5
      cppcms/plugin.h
  2. +9
    -0
      cppcms/service.h
  3. +3
    -0
      private/service_impl.h
  4. +140
    -1
      src/plugin.cpp
  5. +9
    -2
      src/service.cpp
  6. +52
    -5
      tests/plugin_test.cpp
  7. +31
    -19
      tests/test_plugin_so.cpp

+ 154
- 5
cppcms/plugin.h View File

@@ -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 &parameter) { 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 &parameter) { 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


+ 9
- 0
cppcms/service.h View File

@@ -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


+ 3
- 0
private/service_impl.h View File

@@ -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_;
};




+ 140
- 1
src/plugin.cpp View File

@@ -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

+ 9
- 2
src/service.cpp View File

@@ -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

+ 52
- 5
tests/plugin_test.cpp View File

@@ -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;


+ 31
- 19
tests/test_plugin_so.cpp View File

@@ -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



Loading…
Cancel
Save