This document describes the technical details of the implementation of Java Plugins in CycleServer

Java support in components

Components can contain additional Java code that can be shared among Java classes in the component. This Java code is located in the component’s lib/java subdirectory. (A similar facility exists for Python files in an equivalent lib/python directory). All JAR files in that directory are loaded and made available to all classes in the component. This is similar to the site-wide lib/java and lib/python directory in CycleServer, except that it is per-component.

When the component is loaded, Java plugins are found in two ways. The first is by scanning for classes annotated with the com.cyclecomputing.apex.plugin.annotations.PluginClass annotation. The second is by using the JavaPluginLoaderClass configuration attribute. CycleServer will call loadPlugins() on the class given by that attribute. You can use either or both to define your plugins. Here is an example of configuration attribute:

JavaPluginLoaderClass=com.example.YourClass

This would call public static Collection<? extends Plugin> loadPlugins(ComponentAd config) declared on com.example.YourClass and give it the configuration for the component.

A component currently makes all its classes available on the classpath for components that depend on it. In addition, those components automatically have access to all classes from their dependencies.

Importing other plugins

Java plugins can import other plugins in two ways. The first is to get an instance of the plugin registry and use that:

Plugin plugin = Apex.getPluginRegistry().getPlugin("some.plugin");

The second way is to annotate a member variable with the @Import annotation, as described below.

Java API

  • Get an instance of the plugin named foo:

    Apex.getPluginRegistry().getPlugin("foo");
    
  • Call function bar on plugin foo with arguments a and b:

    Apex.getPluginRegistry().getPlugin("foo").call("bar", a, b);
    
  • Call function bar on plugin foo implementing the interface ITest with arguments 1 and “test”:

    Apex.getPluginRegistry().getPlugin("foo", ITest.class).bar(1, "test");
    

Notes: The plugin registry always returns the most recently installed version of plugin.

Annotations

@PluginClass

Identifies this class as a plugin.

@PluginClass
 public class MyPlugin extends JavaPlugin
 {
    ...
 }

@DefaultAd

The name of the plugin comes from configuration of the plugin. Unlike Python or HTML plugins, Java plugins do not use a .cfg file for configuration. Instead, it can be configured in one or both ways: an Ad field on your class annotated with @DefaultAd, or value fields annotated with @Config. @DefaultAd lets you define the entire configuration in one place, using some convenience code to make it more readable in Java.:

@DefaultAd
private static final PluginAd AD = createPluginAd("your.plugin.name",
    define("WebContent", "Dynamic"),
    define("UriPatterns", "/your/plugin/url")
    // ...add additional attributes...
);

@Config

Individual fields can be included in the configuration with the @Config annotation:

@Config
private final String TemporaryDir = "/tmp";

This defines a “TemporaryDir” attribute on the plugin’s config, with an initial value of “/tmp” (but this can be overridden later). In addition, the field is automatically set to the value actually in effect, during construction of the plugin instance. This means that the value is not necessarily the value the field is defined with in Java. Note that the field should be final to ensure proper visibility across threads.

@Import

Plugins can automatically import other plugins with the @Import annotation. For example, this sets service to the plugin named “some.plugin”, assuming that it exists and can be cast to an instance of Service:

@Import("some.plugin")
private final Service service = null;

When the plugin is loaded, the current plugin is assigned to the variable automatically. (This works for both plugins you instantiate and return from your loader, as well as plugins instantiated automatically.) Importing plugins this way also flags your plugin with a dependency on “some.plugin”, so that when some.plugin is reloaded, your plugin will be as well. Note that the field should be final to ensure proper visibility across threads.

@Resources

The resources on a Java plugin can be specified as well via an annotation. This creates a resource called “certificate.txt” from the file named “certificate.txt” in your JAR file, relative to the plugin class:

@Resources
public static void createResources(JavaPluginResources resources)
{
  resources.addFromClasspath("certificate.txt");
  // ...add additional resources...
}

The JavaPluginResources interface supports more complex ways of creating resources as well.

Implementation details

Some system code must have access to the classes defined in plugins. This is set as the thread-specific classloader, which you can get with Thread.currentThread().getContextClassLoader(). This is the standard Java way to get the classloader intended for your thread.