1

I am writing a Spring Boot API application, which has to have a plugin architecture. Following is a typical journey:

  1. My App can execute a task (taking some input and returning an output)
  2. I have an interface called TaskExecutor with a few methods (for example: execute(Object... inputArgs))
  3. Users of my app write a plugin to handle a specific task (User simply implements the TaskExecutor.execute(Object... args) method to suit the requirements of the task). The plugin is packaged as a .jar
  4. I have a screen in my app called Install new Plugin where user can upload the .jar file containing the plugin. For example, the user uploads a new plugin called send-email-plugin-1.0.jar and defines a new Task Type called Send Email.
  5. Once uploaded, the system loads the jar to its classpath.
  6. Whenever the user creates a new task with type Send Email and runs it, the implementation of TaskExecutor in the send-email-plugin-1.0.jar is called.

I want to meet the requirements below:

  1. The plugin should be loaded at runtime
  2. It should be unloaded / updated (when there is a new .jar file) on demand
  3. I must be able to sandbox it and allow / restrict access to certain resources on the host machine.
  4. The host application does not need to access any beans from the plugin - we can treat the plugin as a no-spring piece (for lack of a better term)

I have gone through articles on OSGi, PF4j and others, but can't seem to wrap my head around them - they look complicated for my use case (I'm not sure)

What I have tried so far:

As a very basic piece of code, I tried doing something like this (assuming I have the user's uploaded jar file stored somewhere in the file system).

@SuppressWarnings({ "rawtypes", "resource" })
    // pluginName: name of the plugin (or the JAR file)
    // entryPointClass: full name of the implementation class of TaskExecutor within this plugin. For ex: com.myorg.plugins.EmailTaskExecutorImpl
    public synchronized TaskExecutor loadAdapter(String pluginName, String entryPointClass) {
        try {
            URL jarUrl = getJarAsURL(pluginName);
            
            ClassLoader systemClassLoader = TaskExecutor.class.getClassLoader();
            ClassLoader customLoader = new URLClassLoader(new URL[] {jarUrl}, systemClassLoader);
            Class pluginEntryPoint = customLoader.loadClass(entryPointClass);
            Object pluginEntryPointObject = pluginEntryPoint.newInstance();
            return (TaskExecutor) pluginEntryPointObject;
        } catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            log.error("Error loading JAR for {}", pluginName, e);
            return null;
        }
    }

It is able to do load the plugin at runtime and execute the Job. But, obviously it doesn't do the following:

  • Does not handle updates (what if I want to upgrade the plugin to version 2.0?)
  • It is not sandboxed. Has full access to server. How can I prevent that?
  • Any thread safety / vulnerabilities that I have to take into account?

Request someone to point me in the right direction.

Thanks, Sriram

Sriram Sridharan
  • 720
  • 18
  • 43
  • OSGi is complicated because of the requirements that need to be satisfied. You can try and craft your own solution, or you can go with a defined solution. – Gilbert Le Blanc Dec 15 '21 at 13:38

0 Answers0