Modular Web Applications with OSGi – Part 1- Modules and Dynamic Menus

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInDigg thisShare on RedditPin on PinterestPrint this pageEmail this to someone

This is beginning of a series of posts regarding developing modular Java web applications with OSGi. I must add that these articles are written around my own way forward in OSGi relevant technologies and are written as I move ahead with experimenting and experiencing relevant technologies. Therefore, if you find any better method of achieving same objectives, please feel free to let me know your valuable thoughts with a comment.

Throughout the series of posts a fully dynamic, modular web application will be built that can be extended by installing different modules on the fly. Main web application (base) will have the ability of discovering available modules and integrating them into the application. This integration happens without any application restarts or refreshes and if any of the module goes down (may be due to a maintenance task), module will be disintegrated from the main application without any application restarts or refreshes.  Therefore, relevant approach will not cause any downtime for the overall application during addition or removal of  any particular module.

Additional details regarding advantages of using OSGi, as well as modular design, is available in WhyOSGi page from OSGi Alliance web site.


Part 1 – Introduction

With the part 1 of the series we will be creating a very simple menu structures that gets dynamically organized according to modules getting integrated and disintegrated from the base application. This simple application will be further extended in upcoming parts of the series.

 

Getting Source Code

Checkout the sample source code (from tag “Part1”) from GitHub using below commands :

git clone https://github.com/ayomawdb/Blog_OSGi.git Blog_OSGi

cd Blog_OSGi

git checkout tags/Part1

It is possible to use a text editor to view the source code explained below, but if you prefer Eclipse IDE, you may create a workspace in “Blog_OSGi” directory that you cloned the git repository and import all the projects using “Import Existing Maven Project” option. You might have to install plugin connectors required (which will be automatically handled by IDE) and if your IDE version does not have plugin connectors for maven-bundle-plugin you will have to mark it ignore as suggested by the IDE.

 

Walk through 

Below is the module structure of the application.

  • com.ayomaonline.osgi.parent
    • com.ayomaonline.osgi.main
    • com.ayomaonline.osgi.main.api
    • com.ayomaonline.osgi.modules
      • com.ayomaonline.osgi.modules.user
      • com.ayomaonline.osgi.modules.message

Maven parent module has three main modules as its modules. “com.ayomaonline.osgi.main” is the main application (base) which is going to discovering available modules and organize them accordingly. “com.ayomaonline.osgi.main.api” is the API exposed by the base application, containing interfaces used to define common contracts expected by the main application from application modules.

It is a best practice to bundle API packages separately, specially in OSGi environments. This approach allows avoiding common class loading problems such as “Split Packages“. An interesting discussion regarding bundling API packages is available at API Design OSGi Wiki.

“com.ayomaonline.osgi.modules” is just a parent project for all the modules. Two modules were made available in the Part1 which are “user” module and “message” module.

 

Base Application 

“com.ayomaonline.osgi.main.Activator” which is the activator for the main application creates an instance of ModuleTracker and stars tracking for service instances implementing “com.ayomaonline.osgi.main.api.Module”  interface.

try {
  tracker = new ServiceTracker(context, Module.class.getName(), new DefaultModuleTrackerCustomizer(context));
  tracker.open();
} catch (NoClassDefFoundError error) {
  error.printStackTrace();
}

If such service instance is found “com.ayomaonline.osgi.main.tracker.DefaultModuleTrackerCustomizer” will call “getMenuItems()” method of the service and fetch menu items exposed by the particular service. Relevant menu items are added and recorded in the “com.ayomaonline.osgi.main.registrar.MenuItemRegistrar”.

Module service = (Module) context.getService(reference);
MenuItemRegistrar.addMenuItem(service.getClass().getName(), service.getMenuItems());

Same as tracking the addition of the service, service removals will be tracked and menu items added by the removed module will be removed from MenuItemRegistrar.

MenuItemRegistrar.removeMenuItem(service.getClass().getName());

Going trough the code, you will notice that the modules are designed using OSGi low level service API. It is true that using Declarative Services (DS) or Blueprint based services is better than working with OSGi low level API, but there are situations where low level API is the optimal solution. Low level API was used in ModeTracker because in order to get the best out of DS or Blueprint based services, it is necessary for the base application to declare what are the modules that will be connected with the base application beforehand (in XML configuration).  Hence, we will be using low level API for module tracking and, DS and Blueprint services will be covered in a future part of the series.

 

Module (User Module)

“Activator” of the “com.ayomaonline.osgi.modules.user” bundle registers “UserModule” class as a OSGi service.

context.registerService(Module.class.getName(), new UserModule(), null);

 

“UserModule” defines menu items exposed by the user module by implementing the getMenuItems() method of “Module” interface.

public List<MenuItem> getMenuItems() {
  List<MenuItem> menuItemList = new ArrayList<MenuItem>();

  menuItemList.add(new MenuItem("user.add", "/user/add"));
  menuItemList.add(new MenuItem("user.manage", "/user/manage"));

  return menuItemList;
}

 

In conclusion, when base application along with two modules is started in an OSGi container, base application will start looking for services implementing Module interface. Once any new module is found, base module will fetch exposed menu items by calling modules getMenuItems() method and register them within the base application. If a module goes offline, base application will be notified and it will remove menu items added by the particular module from the register.

Finally the “index.jsp” file available in the base application will get available menu items from the “MenuItemRegistrar” and print them. Using scriptlets in the JSP file is not the best practice, but it was necessary to keep it simple in the Part 1 and focus more on OSGi relevant details.

 

	<ul>
		<%
			for (String module : MenuItemRegistrar.getMenuItems().keySet()) {
				List<MenuItem> menuItemList = MenuItemRegistrar.getMenuItems().get(module);
				for (MenuItem menuItem : menuItemList) {
					%>
						<li><a href="<%=menuItem.getPath()%>"><%=menuItem.getKey() %></a></li>
					<%
				}
			}
		%>
	</ul>

 

Testing

Pax Runner is a tool usable to provision OSGi bundles in all major open source OSGi framework implementations and we are using Apache Felix in this project. You may choose a different implementation if necessary just by editing “maven-pax-plugin” configuration available in parent pom.xml file. In order to run the sample project using Pax Runner execute maven goals with below command (Pax Runner might take some time to do the first start-up of the project, but any further execution will start-up faster) :

cd com.ayomaonline.osgi.parent

mvn clean install pax:run

During start-up of the application you will notice that the console prints below messages:

08

Above messages indicate that the base package as well as two modules we have created has started up. “ADDING MODULE” message is printed by the “ModuleTracker” of the base application and it indicates that the base application has identified available modules and has registered relevant modules during start-up.

“[3]” appended in-front of each message indicates that during the start-up of the the application bundles, there has been no “LogService” instance available to forward application logs. Hence, “Logger” has decided to go ahead with using the alternative “sysout” statement. This behavior depicts the importance of using a “ServiceTracker” while using OSGi low level service APIs and if ServiceTracker was not used, application might end up with not printing log messages or will fail with NullPointerExcepton trying to access unavailable service instances (depending on implementation).

  • Navigate to “http://127.0.0.1:8080/osgi” to check the menu structure generated by the base module:

 01

  • Use command “lb” to list bundles currently installed in the OSGi environment :

02

  • Stop “User” module by executing “stop 8” (8 is the bundle ID for “user” module)

04

  • Navigate to “http://127.0.0.1:8080/osgi” again to check the menu structure generated by the base module:

03

  • Restart the “User” module by executing “start 8”  (8 is the bundle ID for “user” module)

05

  • Navigate to “http://127.0.0.1:8080/osgi” again to check the menu structure generated by the base module:

01

 

As per above observations, it is clear that the base application correctly identifies module changes and adjust the menu structure dynamically. This is just an simple example of using the demonstrated method of modular application development using OSGi services and ServiceTracker(s). You may extend the sample in exchanging different information between bundles and it is possible to extend same using client side technologies (ajax / web-sockets) to dynamically adjust menu structure without a page refresh (even-thought it could be an overkill in our scenario) . Please feel free to let me know your suggestion as well as your applications of this approach in comments.

 

Next Step

In next post we will be further enhancing the application to dynamically register and unregister web resources (“Servlet” or “Static Resources”) available in modules with the base web application, using technologies including Pax Web Extender. At the completion of Part 2, modules will be able to dynamically attach not only menu items, but also web resources required to render menu items.

Part 2 – MODULAR WEB APPLICATIONS WITH OSGI – PART 2 – DYNAMIC WEB RESOURCES

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInDigg thisShare on RedditPin on PinterestPrint this pageEmail this to someone

2 comments on “Modular Web Applications with OSGi – Part 1- Modules and Dynamic Menus

  1. sekaijin October 27, 2016 5:40 PM

    Hi,
    I’ve redefined the Tracker Module to make it generic (reusable in all project)
    package com.ayomaonline.osgi.main.tracker;

    import org.osgi.framework.BundleContext;
    import org.osgi.util.tracker.ServiceTracker;
    import org.osgi.util.tracker.ServiceTrackerCustomizer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class ModuleTracker extends Thread {
    private ServiceTracker tracker;
    private Class moduleTrackerCustomizer;
    private String className;
    private Logger log;
    private BundleContext context;

    public ModuleTracker(BundleContext context, Class service, Class trackerCustomizer) {
    this.log = LoggerFactory.getLogger(getClass());
    this.className = service.getName();
    this.context = context;
    this.moduleTrackerCustomizer = trackerCustomizer;
    }

    public void run() {
    try {
    this.tracker = new ServiceTracker(context, className, moduleTrackerCustomizer.newInstance());
    this.tracker.open();
    } catch (Throwable e) {
    log.error(e.getMessage(), e);
    }
    }

    public synchronized void close() {
    if (tracker != null) {
    tracker.close();
    }
    }
    }

    In Activator, simply instantiate and start it.
    (new ModuleTracker(context, Module.class, DefaultModuleTrackerCustomizer.class)).start();
    The DefaultModuleTrackerCustomizer.class just implements ServiceTrackerCustomizer with default constructor
    in addingService and removedService you can get context with reference.getBundle().getBundleContext();

    bye

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">