Ice supports a plugin facility that allows you to add new features and install application-specific customizations. Plugins are defined using configuration properties and loaded dynamically by the Ice run time, making it possible to install a plugin into an existing program without modification.
Ice uses the plugin facility to implement some of its own features. Most well-known is IceSSL, a plugin that adds a secure transport for Ice communication (see
Chapter 38). Other examples include the logger plugin (see
Section 28.19.4) and the string converter plugin (see
Section 28.23.6).
module Ice {
local interface Plugin {
void initialize();
void destroy();
};
};
The lifecycle of an Ice plugin is structured to accommodate dependencies between plugins, such as when a logger plugin needs to use IceSSL for its logging activities. Consequently, a plugin object’s lifecycle consists of four phases:
After all plugins have been constructed, the Ice run time invokes initialize on each plugin. The order in which plugins are initialized may be specified using a configuration property (see
Section 28.24.3), otherwise the order is undefined. If a plugin has a dependency on another plugin, you must configure the Ice run time so that initialization occurs in the proper order. In this phase it is safe for a plugin to spawn new threads; it is also safe for a plugin to interact with other plugins and use their services, as long as those plugins have already been initialized.
If initialize raises an exception, the Ice run time invokes
destroy on all plugins that were successfully initialized (in the reverse order of initialization) and raises the original exception to the application.
extern "C"
{
ICE_DECLSPEC_EXPORT Ice::Plugin*
functionName(const Ice::CommunicatorPtr& communicator,
const std::string& name,
const Ice::StringSeq& args);
}
You can define the function with any name you wish. We recommend that you use the
ICE_DECLSPEC_EXPORT macro to ensure that the function is exported correctly on all platforms. Since the function uses C linkage, it must return the plugin object as a regular C++ pointer and not as an Ice smart pointer. Furthermore, the function must not raise C++ exceptions; if an error occurs, the function must return zero.
The arguments to the function consist of the communicator that is in the process of being initialized, the name assigned to the plugin, and any arguments that were specified in the plugin’s configuration.
package Ice;
public interface PluginFactory {
Plugin create(Communicator communicator,
String name,
String[] args);
}
The arguments to the create method consist of the communicator that is in the process of being initialized, the name assigned to the plugin, and any arguments that were specified in the plugin’s configuration.
The create method can return
null to indicate that a general error occurred, or it can raise
PluginInitializationException to provide more detailed information. If any other exception is raised, the Ice run time wraps it inside an instance of
PluginInitializationException.
namespace Ice {
public interface PluginFactory
{
Plugin create(Communicator communicator,
string name,
string[] args);
}
}
The arguments to the create method consist of the communicator that is in the process of being initialized, the name assigned to the plugin, and any arguments that were specified in the plugin’s configuration.
The create method can return
null to indicate that a general error occurred, or it can raise
PluginInitializationException to provide more detailed information. If any other exception is raised, the Ice run time wraps it inside an instance of
PluginInitializationException.
The value of entry_point is a language-specific representation of the plugin’s factory. In C++, it consists of the name of the shared library or DLL containing the factory function, along with the name of the factory function. In Java, the entry point is the name of the factory class, while in .NET the entry point also includes the assembly.
The language-specific nature of plugin properties can present a problem when applications that are written in multiple implementation languages attempt to share a configuration file. Ice supports an alternate syntax for plugin properties that alleviates this issue:
Ice.Plugin.Name.cpp=... # C++ plugin
Ice.Plugin.
Name.java=... # Java plugin
Ice.Plugin.
Name.clr=... # .NET (Common Language Runtime) plugin
Plugin properties having a suffix of .cpp,
.java, or
.clr are loaded only by the appropriate Ice run time and ignored by others.
Refer to Appendix C for more information on these properties.
If a plugin has a dependency on another plugin, you must ensure that Ice initializes the plugins in the proper order. Suppose that a custom logger implementation depends on IceSSL; for example, the logger may need to transmit log messages securely to another server. We start with the following C++ configuration:
The problem with this configuration is that it does not specify the order in which the plugins should be loaded and initialized. If the Ice run time happens to initialize
MyLogger first, the plugin’s
initialize method will fail if it attempts to use the services of the uninitialized IceSSL plugin.
Using the Ice.PluginLoadOrder property we can guarantee that the plugins are loaded in the correct order.
Appendix C describes this property in more detail.
PluginManager is the name of an internal Ice object that is responsible for managing all aspects of Ice plugins. This object supports a Slice interface of the same name, and an application can obtain a reference to this object using the following communicator operation:
module Ice {
local interface Communicator {
PluginManager getPluginManager();
// ...
};
};
module Ice {
local interface PluginManager {
void initializePlugins();
Plugin getPlugin(string name);
void addPlugin(string name, Plugin pi);
};
};
The initializePlugins operation is used in special cases when an application needs to manually initialize one or more plugins, as discussed in the next section.
The getPlugin operation returns a reference to a specific plugin. The
name argument must match an installed plugin, otherwise the operation raises
NotRegisteredException. This operation is useful when a plugin exports an interface that an application can use to query or customize its attributes or behavior.
Finally, addPlugin provides a way for an application to install a plugin directly, without the use of a configuration property.
It is sometimes necessary for an application to manually configure a plugin prior to its initialization. For example, SSL keys are often protected by a passphrase, but a developer may be understandably reluctant to specify that passphrase in a configuration file because it would be exposed in clear text. The developer would likely prefer to configure the IceSSL plugin with a password callback instead; however, this must be done before the plugin is initialized and attempts to load the SSL key. The solution is to configure the Ice run time so that it postpones the initialization of its plugins:
When this property is set to zero, initializing plugins becomes the application’s responsibility. The example below demonstrates how to perform this initialization:
// C++
Ice::CommunicatorPtr ic = ...
Ice::PluginManagerPtr pm = ic‑>getPluginManager();
IceSSL::PluginPtr ssl = pm‑>getPlugin("IceSSL");
ssl‑>setPasswordPrompt(...);
pm‑>initializePlugins();
After obtaining the IceSSL plugin and establishing the password callback, the application invokes
initializePlugins on the plugin manager object to commence plugin initialization.