Ice supports a plug‑in facility that allows you to add new features and install application-specific customizations. Plug‑ins are defined using configuration properties and loaded dynamically by the Ice run time, making it possible to install a plug‑in into an existing program without modification.
Ice uses the plug‑in facility to implement some of its own features. Most well-known is IceSSL, a plug‑in that adds a secure transport for Ice communication (see
Chapter 41). Other examples include the logger plug‑in (see
Section 32.19.4) and the string converter plug‑in (see
Section 32.24.7).
This section describes the plug‑in facility in more detail and demonstrates how to implement an Ice plug‑in.
32.25.1 Plug‑In API
The plug‑in facility defines a local Slice interface that all plug‑ins must implement:
module Ice {
local interface Plugin {
void initialize();
void destroy();
};
};
The lifecycle of an Ice plug‑in is structured to accommodate dependencies between plug‑ins, such as when a logger plug‑in needs to use IceSSL for its logging activities. Consequently, a plug‑in object’s lifecycle consists of four phases:
After all plug‑ins have been constructed, the Ice run time invokes
initialize on each plug‑in. The order in which plug‑ins are initialized may be specified using a configuration property (see
Section 32.25.3), otherwise the order is undefined. If a plug‑in has a dependency on another plug‑in, you must configure the Ice run time so that initialization occurs in the proper order. In this phase it is safe for a plug‑in to spawn new threads; it is also safe for a plug‑in to interact with other plug‑ins and use their services, as long as those plug‑ins have already been initialized.
If initialize raises an exception, the Ice run time invokes
destroy on all plug‑ins that were successfully initialized (in the reverse order of initialization) and raises the original exception to the application.
In C++, the plug‑in factory is an exported function with C linkage having the following signature:
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 plug‑in 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 plug‑in, and any arguments that were specified in the plug‑in’s configuration.
In Java, a plug‑in factory must implement the
Ice.PluginFactory interface:
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 plug‑in, and any arguments that were specified in the plug‑in’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.
In .NET, a plug‑in factory must implement the
Ice.PluginFactory interface:
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 plug‑in, and any arguments that were specified in the plug‑in’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.
32.25.2 Plug‑In Configuration
Plug‑ins are installed using a configuration property of the following form:
In most cases you can assign an arbitrary name to a plug‑in. In the case of IceSSL, however, the plug‑in requires that its name be
IceSSL.
The value of entry_point is a language-specific representation of the plug‑in’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 plug‑in 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 plug‑in properties that alleviates this issue:
Ice.Plugin.Name.cpp=... # C++ plug‑in
Ice.Plugin.
Name.java=... # Java plug‑in
Ice.Plugin.
Name.clr=... # .NET (Common Language Runtime) plug‑in
Plug‑in 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 D for more information on these properties.
This section discusses additional aspects of the Ice plug‑in facility that may be of use to applications with special requirements.
Plug‑In Dependencies
If a plug‑in has a dependency on another plug‑in, you must ensure that Ice initializes the plug‑ins 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 plug‑ins should be loaded and initialized. If the Ice run time happens to initialize
MyLogger first, the plug‑in’s
initialize method will fail if it attempts to use the services of the uninitialized IceSSL plug‑in.
Using the Ice.PluginLoadOrder property we can guarantee that the plug‑ins are loaded in the correct order.
Appendix D describes this property in more detail.
PluginManager is the name of an internal Ice object that is responsible for managing all aspects of Ice plug‑ins. 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();
// ...
};
};
The PluginManager interface offers three operations:
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 plug‑ins, as discussed in the next section.
The getPlugin operation returns a reference to a specific plug‑in. The
name argument must match an installed plug‑in, otherwise the operation raises
NotRegisteredException. This operation is useful when a plug‑in 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 plug‑in directly, without the use of a configuration property.
It is sometimes necessary for an application to manually configure a plug‑in 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 plug‑in with a password callback instead; however, this must be done before the plug‑in 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 plug‑ins:
When this property is set to zero, initializing plug‑ins 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 plug‑in and establishing the password callback, the application invokes
initializePlugins on the plug‑in manager object to commence plug‑in initialization.