OpenDJ plugin development based on example-plugin
While OpenDS plugin development was fairly well documented, it has evolved with OpenDJ while available information has not. I will try here to share some of my experience which might help you save some time until the plugin API becomes stable enough to be officially documented.
The starting point for documentation is:
You should read the first document listed and try to understand it before you start developing your own plugin, otherwise the rest of the article would not make much sense.
Files, naming and dependencies
The good base to start developing your own extension is the example plugin delivered in the OpenDJ binary distribution as example-plugin.zip. You can start by unzipping it and renaming the folder to your custom name. This is a good moment to think about the name of the plugin since it affects the way supporting classes are generated. Although the folder name has no effect, you would normally use the same name on multiple places in the sources. The name ‘example-plugin’ would generate classes with name ‘ExamplePlugin*’, ‘my-custom-plugin’ would result in ‘MyCustomPlugin*’, while ‘mycustom-plugin’ would turn out to be ‘MycustomPlugin*’ – obviously, the ‘-‘ sign would cause the class names to be generated with camel case and the framework would require the class names to follow the case in order to compile them. The most important thing here is to pick a name and stick to it.
unzip example-plugin.zip mv example-plugin my-plugin
The plugin generation framework depends on Xalan-Java as a workaround for a JDK-related XML processing bug, hence you should download the latest distribution of it and unpack it to the ext/ folder as xalan-j since that’s what build.xml expects to find.
unzip -d my-plugin/ext xalan-j*.zip cd my-plugin/ext mv xalan-j* xalan-j
Apart from Xalan-Java, the build framework depends on ant-contrib tasks which you have to download and deploy inside the folder where ant would find it (or specify the location with the ant command line parameters).
Next step could be to modify build.xml so it fits the purpose of your custom plugin. The properties to change could be:
- name under project tag
- <description> tag
Sources are inside the src/ folder followed by the folders which mark the package name, as usual. You might want to rename the com.example.opends to something more fitting to your company or project. In resource/ folder you would find schema/ and messages/ relevant. The schema file in schema/ folder should follow the name you have chosen in the form ’99-NAME.ldif’, for example: ’99-my-example-plugin.ldif’ – although this would not impact the building process, the naming would be consistent. The messages/ directory contains the properties file(s) with the error messages and their localisation organised in the folders that follow the name of the package. Again, another part which should be renamed following the chosen name.
Note that resources/ folder contains config/ which in turn has a file with LDIF representation of the configuration defined in the XML file. That file (and folder for that matter) is not needed contrary to what README file says. Previously this file was used to configure the plugin by injecting it to the config.ldif file, but as the software evolved, this step is now performed with the dsconfig command. More about that later.
You will find your development process bouncing between the: source files which implement the plugin functionality, the configuration file for the plugin which is used to generate dependent classes, the properties file which defines the messages and, to smaller extent, the schema file.
The files we find in the sources folder of the example plugin are: ExamplePlugin.java, ExamplePluginConfiguration.xml, Package.xml and package-info.java. ‘ExamplePlugin*’ files are the ones that should be renamed to match the name you have chosen, say, if you have chosen ‘my-example-plugin’ then they should be: MyExamplePlugin.java and MyExamplePluginConfiguration.xml. Next, you should modify MyExamplePluginConfiguration.xml not only to match your configuration needs but also your naming choice (‘name’ property, ‘package’ property, etc.). Based on the information in the configuration file, the build framework would generate classes in src-generated/ folder which would follow the names and packages properties, so if you do not name everything consistently and refer to it in your sources, the compilation process would fail. Needless to say, the contents of Packages.xml should match the rest of the configuration.
The relationship of the most important files is the following:
- src/com/example/opends/ExamplePlugin.java contains the functionality of the plugin;
- src/com/example/opends/ExamplePluginConfiguration.xml defines the configuration parameters for the plugin (set with the dsconfig command) which can be accessed via ‘config’ object inside the code;
- resources/messages/com/example/opends/messages/example_plugin.properties defines the localised messages (for the given locale) which can be accessed from the code in a generic way, and
- resources/schema/99-example-plugin.ldif is the schema definition for the parameters defined in the configuration file.
As you develop the functionality of the plugin, you will find it necessary to update the configuration and/or the messages which would require you re-generate the dependant files, otherwise if you use an IDE for development, the changes would not be reflected.
Notes on the configuration
The configuration parameters defined in the configuration file are represented in the directory as LDAP entries, hence when you define new parameters you are really defining new object class and new attributes. Logically, in order to have the directory updated with the plugin configuration, it has to contain the schema with the definition of the object classes and attributes you want to add. The parameters need not be reflected in the schema files during the time of development – it is only important to update it before you install the plugin, as the schema definition is important for the directory server to be able to create the plugin and create it’s configuration.
To get the better idea of how to set up the schema file, you can compare the ExamplePluginConfiguration.xml with 99-example-plugin.ldif. Another helpful example might be found in the Samba password syncronisation plugin: SambaPasswordPluginConfiguration.xml, 99-samba-password-plugin.ldif.
Creating a NetBeans project
If you want to develop your plugin as a NetBeans project, here is a simple way to do it:
- click on the New Project button;
- choose Java Free-form Project from the Java category;
- set the Location to the folder of your plugin;
- click Next
- click Next
- uncheck Separate Classpath…, and add OpenDJ.jar
- click Finish
This configuration would relay on the existing build.xml script to build everything, so you might want to update the menu with relevant targets such as compileadmin, generate-messages, package, install, etc.
Compiling and installing
If your code relies on external libraries, such as OpenDJ SDK, they should go to the ext/ directory.
The most relevant ant targets are:
- compileadmin – generates the dependent classes (based on the XML file);
- generate-messages – generates the messages (based on the properties file);
- compile/package – compiles the sources/creates the jar ready to install, and
- install – installs the plugin
Installation of the plugin is not a single step which depends on the ant target, but requires various actions. Running the install target with ant would copy the plugin JAR, the schema and the configuration LDIF to the relevant folders under the location specified by the opends.install.dir property in build.xml (respectively, by default: ../lib/extensions, ../config). As noted previously, the configuration LDIF is obsolete and should be removed so that it is not copied at all. The external libraries would not be copied (you would have to do that by hand), and the installation would have effect only upon the restart. Startup messages would indicate if the plugin was loaded and if there were any errors with the schema file.
The installation does not end here as the plugin has to be registered, configured and enabled in order to be used. All of that can be done in a single line with the dsconfig tool. For example:
dsconfig -X -n create-plugin --plugin-name "My Example Plugin" \ --type my-example-plugin --set enabled:true --set some-property:value
Most of the time, when a problem occurs, you would want to know what is happening inside your code. For this purpose you can define a debug logger and log whatever you find useful. To do that, you should create a DebugTracer object as a static member of your class:
private static final DebugTracer TRACER = DebugLogger.getTracer();
The messages logged with the TRACER need debugging to be enabled in the directory and you can do that with the dsconfig command:
- create the debug target:
dsconfig -X -n create-debug-target \ --publisher-name "File-Based Debug Logger" \ --target-name com.example.opends.ExamplePlugin --set debug-level:all
- enable the debug log:
dsconfig -X -n set-log-publisher-prop \ --publisher-name "File-Based Debug Logger" --set enabled:true
Although still work in progress, Maven archetype for OpenDJ plugins is an easier alternative. It will perform all the steps necessary to keep you focused on coding and less on the internal dependencies of the example plugin.