Discount week - May Swift week
Save up to 80 % on our Swift e-learning courses. Only this week!
Get up to 60 % extra points for free! More info

Lesson 10 - Java Server - Plugin System

In the previous lesson, Java Server - Event bus, we focused on propagating events across the server using an event bus.

Today we're going to design and implement a system with which we can extend server functionality easily.

Plugins

To begin with, it's a good idea to define what a plugin is. Plugins will represent individual server functionalities. Under the term server functionality you can imagine:

  • user management
  • database access
  • chat
  • communication with an external service
  • and more...

Designing the Plugin System

Our plugin system will be relatively simple. It's task will be to load all available plugins and initialize them when the server starts. In the future, we'll improve this system with loading plug-ins from external jar files so we don't have to recompile the server every time someone wants to add a new feature.

Implementation

We'll start the implementation by designing an interface that will represent a plugin. We'll name the interface IPlugin and put it in a separate plugins package. The interface will contain a getName() method, which, as its name implies, will return the plugin's name. Next, there will be an init() method in which the plugin will be initialized. Using a registerMessageHandlers() method, the plugin will be able to register all events to which it'll respond in the future. In a setupDependencies() method, it'll be possible to wire individual plugins together. The whole interface looks like this:

package cz.stechy.chat.plugins;

public interface IPlugin {
    String getName();
    void init();
    void registerMessageHandlers(IEventBus eventBus);
    void setupDependencies(Map<String, IPlugin> otherPlugins);
}

In the same package, we'll create an enumeration that will include all plugins embedded in the server. Let's name the enum simply Plugin. The enumeration will contain a constant of the Class<? extends IPlugin> type. Remember to add a constructor:

public enum Plugin {
    ;
    public final Class<? extends IPlugin> clazz;
    Plugin(Class<? extends IPlugin> clazz) {
        this.clazz = clazz;
    }
}

This constant will refer to the class that implements the plugin. For now, the enum is empty, so a semicolon is needed at the beginning.

We'll now register all modules in Google guice to instantiate them automatically. We'll create a new PluginModule module inherited from the AbstractModule class provided by the guice library and implement the configure method:

public class PluginModule extends AbstractModule {

    @Override
    protected void configure() {
        MapBinder < String, IPlugin > pluginBinder = MapBinder.newMapBinder(binder(), String.class, IPlugin.class);
        for (Plugin plugin: Plugin.values()) {
            pluginBinder.addBinding(plugin.name()).to(plugin.clazz).asEagerSingleton();
        }

        // TODO load external plugins
    }
}

In this method we'll prepare a pluginBinder variable, which we'll use to bind all plugin implementations with our IPlugin interface. More about MapBinder implementation can be found on wiki guice. We'll do the binding in a loop in which we iterate through the plugin enum embedded directly in the server. It's worth mentioning just to call the asEagerSingleton() method, which says that whenever we request a specific plugin instance, we always get the same one. We'll keep loading external plugins for later lessons.

Next, we'll register the newly created module in guice. We'll add a new PluginModul module in the Server class, in the main() method, where the Injector class is instantiated:

final Injector injector = Guice.createInjector(new ServerModule(), new PluginModule());

Finally, we'll edit the Server class. We'll add constants of the Map<String, IPlugin> and IEventBus types. The class will accept these constants in the constructor:

@Inject
public Server(IParameterFactory parameterFactory, IServerThreadFactory serverThreadFactory,
    IEventBus eventBus, Map<String, IPlugin> plugins) {
    this.parameterFactory = parameterFactory;
    this.serverThreadFactory = serverThreadFactory;
    this.eventBus = eventBus;
    this.plugins = plugins;
}

Now we have to initialize the plugins. To do this, we'll create a private initPlugins() method to take care of the initialization:

private void initPlugins() {
    for (IPlugin plugin: plugins.values()) {
        plugin.init();
    }
    for (IPlugin plugin: plugins.values()) {
        plugin.registerMessageHandlers(messageRegistrator);
    }
    for (IPlugin plugin: plugins.values()) {
        plugin.setupDependencies(plugins);
    }
}

First, we call the init() method over all plugins and give them the opportunity to initialize. Next, we register listeners for the corresponding events and finally let the plugins set their dependencies on each other. We'll call the initPlugins() method before the server thread starts.

Test Plugin

For demonstration purposes, we'll create a simple plugin that will do nothing but write its name to the console during the initialization phase. In the plugins package, we'll create another package named hello. In this package, we'll create a HelloPlugin class that will implement our IPlugin interface. We'll implement the required methods as follows:

package cz.stechy.chat.plugins.hello;

public class HelloPlugin implements IPlugin {

    @Override
    public String getName() {
        return "HelloPlugin";
    }

    @Override
    public void init() {
        System.out.println("Plugin initialization: " + getName());
    }
}

We'll add the plugin to the Plugin enum. Keep in mind that it's not enough to just specify the name, but also the class that implements the plugin:

public enum Plugin {
    HELLO(HelloPlugin.class);
    ...
}

That's all. When you start the server, you get a message that the plugin has been initialized.

That would be all for today.

In the next lesson, Java Server - Local Area Network Propagation (Part 1), we'll prepare the basis for making the server visible in the local network.


 

 

Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!