Skip to content

Plugin Architecture

YuriyZ edited this page Oct 22, 2018 · 38 revisions

Plugin Architecture

Goal of this page is to give a few proposals how new oxauth/oxtrust architecture may look to push discussion in more constructive way forward.

Content

Preface

High level idea is to have two versions of products : CE (free) and EE (that consists of individual plugins). The topic itself is very wide and we can hit different paths here. However after Mike's comment about Weld, it seems we need to stick to what we have. Thus I've concentrated on two main ways how can we accomplish CE/EE with Weld.

Proposal 1 : static load with Weld (Interceptors, Decorators, Events)

oxAuth/oxTrust both are based on Weld which is the reference implementation of CDI (Contexts and Dependency Injection for the Java EE Platform). There are built-in concept of extension

A portable extension may integrate with the container by:

  • Providing its own beans, interceptors and decorators to the container
  • Injecting dependencies into its own objects using the dependency injection service
  • Providing a context implementation for a custom scope
  • Augmenting or overriding the annotation-based metadata with metadata from some other source

With Weld Interceptors/Decorators it is possible to intercept any Bean and inject own code or add new beans.

How will it work?

There can be 3 parts

  • interface part of the server (server-interface) - this is used to provide interfaces and annotations for interception which are used by both plugins (extensions) and core (core implementation)
  • core of the server (server-core) - main CE implementation
  • plugins (server-plugin) - this are Weld extensions that can intercept/decorate CE implementation or add new functionality.

Example:

First declare marker interface for interception @Interceptable (or @Extendable or whatever we like) in server-interface.

Then in server-core (CE) mark method or class as interceptable.

@ApplicationScoped
public class ServerFlowService {

    @Interceptable
    public void handle(String param) {
        // handler code here
    }
}

Then in plugin we can

@Interceptor
@Interceptable
@Priority(2)
public class ServerFlowServiceInterceptor {
     @AroundInvoke
    public Object manageTransaction(InvocationContext ctx) throws Exception {
        // do whatever we wish here
        return ctx.proceed();
    }
}

In interceptor we can differentiate by class type and method name. More advanced way is to use decorators

How loading/unloading is performed? Loading and unloading is performed via jar files and are static. CDI by it's nature is static and therefore it by intention does not provide API for dynamic injection into CDI.

Advantages

  • We have everything out of the box in Weld.
  • Interception and new functionality can be provided via Weld extensions

Disadvantages

  • Main disadvantage is static loading. Restart of application is required.

Prove of concept I've quickly drafted 3 modules inside oxauth and checked how easy is it to inject. It looks very easy and robust. Please check https://github.com/GluuFederation/oxAuth/tree/yuriyz_plugin_experiment_v3.1.5

Proposal 2 : dynamic load with Weld and OSGi via Pax CDI

If we are ok with static nature of plugins/extension then best is to proceed with Weld's extension points (Proposal 1) but if it is not acceptable then we can add dynamic loading/unloading via OSGi and Pax CDI.

Pax CDI unites two powerful programming models, Contexts and Dependency Injection (CDI) from Java EE 6 and dynamic modules and services from OSGi.

In OSGi plugins/extensions are called bundles. The core feature with Pax CDI is a bean from bundle A may inject a (proxy of a) bean provided by bundle B, if B has published the bean in the OSGi service registry.

There are different OSGi implementations:

  • Apache Felix (Open source)
  • Concierge OSGi (Open source)
  • Equinox OSGi (Open source)
  • Knopflerfish (Open Source)
  • Bosch IoT Gateway Software (Commercial)
  • Eclipse Gemini (Open Source)

Google shows that Pax CDI is often referenced with Apache Felix. Therefore on this page I will stick to Apache Felix which seems to be good enough for oxauth/oxtrust.

Felix can be started as easy as this

java -jar bin/felix.jar

It goes with built-in shell which allows to install and start bundles (after installation bundle is not started). With shell it is easy to install, start, stop each bundle separately/dynamically.

Example

g! install file:/path/to/bundle/bundle.jar
g! lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (3.0.0)
    1|Active     |    1|Apache Felix Bundle Repository (1.6.2)
    2|Active     |    1|Apache Felix Gogo Command (0.6.0)
    3|Active     |    1|Apache Felix Gogo Runtime (0.6.0)
    4|Active     |    1|Apache Felix Gogo Shell (0.6.0)
    5|Installed  |    1|Example Bundle (1.0.0)
g! start 5
Hello from Bundle 5.

More explanation here in Felix docs.

In short each bundle must declare interface and implementation. Interface part must be shared between bundles statically if we wish to keep system typesafe. Then each time bundle 1 needs service from bundle 2 it use look up (usually via tracker). Tracker handles complexity of dealing with dynamic service availability.

tracker = new ServiceTracker(context, context.createFilter("(&(objectClass=" + DictionaryService.class.getName() + ")" + "(Language=*))"), null);
tracker.open();

// Get the dictionary service from bundle 2, or get null if bundle 2 is not available.
DictionaryService dictionary = (DictionaryService) tracker.getService();

Service with Pac (for more examples check this )

@Inject
@OsgiService(dynamic = true)
private Instance<IceCreamService> iceCreamServices;

Advantage

  • Full support of dynamic loading/unloading of bundles (plugins/extensions)

Disadvantage

  • Pax CDI only works with the traditional Pax Web Jetty container. It does not yet support the Pax Web Tomcat container. If we decide to move to another Web Application container (other then Jetty) we can face problems. See
  • OSGi infrastructure is very flexible and dynamic but at the same time due to big amount parts and dynamic nature there appear whole set of dynamic related issues (e.g. race condition and synchronization). Also we need to try what will happen if bundle 1 is executing bundle 2 code and we unload bundle 2 during execution.
  • With OSGi oxauth/oxtrust developer must produce concurrency safe code with appropriate locking. To get an idea please check felix examples.

FAQ

Will (15 Oct 18): I don't think we want to use a license file in the code. I think it would be easier to license access to a repo with EE extensions

How exactly are we going to license access? (Some interceptor that enforce license?) It would be nice to get more details on it.

What framework we want to use for plugins? We have Weld as CDI in oxauth and oxtrust. So ideally we have to find how to bring plugin model into live with Weld.

Do we want core of the program be plugin itself too? With Weld no. We can do it without Weld but it looks as too much effort.

Do we want to support versioning of the plugins? Yes, each plugin sticks to concrete version of the core.

Clone this wiki locally