http://www.developer.com/

Back to article

Create Reusable OSGi Components for Eclipse Equinox


March 17, 2010

Largely because it allows multiple components to interact efficiently and securely in a Java Virtual Machine (JVM), the Open Service Gateway Initiative (OSGi) specification claims the title: "The Dynamic Module System for Java." With its rich security model, OSGi allows each component to run in its own secured environment. However, by setting the proper permissions, you can enable OSGi components to reuse other components, a capability that differentiates OSGi from other containers. This is what makes the OSGi framework lightweight and reusable.

The core of OSGi is a framework that defines:

  • An application lifecycle management model
  • A service registry
  • An execution environment
  • An atomic part of the application that is composed of modules or components

These definitions standardize how you define modules or components and how you manage the lifecycle of these components. The beauty of the OSGi runtime is that you can install, update, or remove components on the fly—without interrupting existing operations. This feature makes OSGi ideal for domains where component/module provisioning must occur without interruptions to the existing operations (e.g., grid and cloud computing). OSGi also can expose Plain Old Java Object (POJO) services at each component level, a service-oriented way for components to communicate with each other.

In this article, we walk through the development of a thick client: a user login component that authenticates users before granting access to back-end resources. Along the way, we explore reusability and size reduction of individual bundles, as well as features such as OSGi services and Eclipse plug-in extension points.

Developing a Reusable User Login Solution for OSGi

The architecture for the user login solution is composed of two layers, each containing a bundle/component. One bundle comprises the UI part (UserLoginUI) and the other provides the required services (UserLoginServices) for the solution. The UserLoginUI bundle helps to collect the credentials from the user, and the UserLoginServices bundle validates and saves the credentials for further use.

The rest of the article provides instructions for developing this solution in Eclipse.

Create a New Service

First, you need to create a new service:

  1. Click on the File tab, select the New option, and then select the Other... option (see Figure 1).

  2. Select the Plug-in Development wizard and then Plug-in Project (see Figure 2).

  3. To create a service, you need to change the target platform. The default selection is an Eclipse version. You need to change that to an OSGI framework, select Equinox from the dropdown list, and then click Next (see Figure 3).

  4. Click the Finish button, and you have created a bundle having a blank service (see Figure 4).

Register Services

The User Login solution has two services:

  • IValidateService: This service validates user credentials against a credential store. It can have multiple implementations, depending on the type of credential storage.
  • IContextDataService: This service hides the implementation classes from the consumer of the services, leaving it to the bundle's discretion which implementation is made available to the consumer.

Because OSGi allows updates to the bundles without interrupting dependent bundles, you can update these services in a highly dynamic manner without effecting the service consumer.

Before using a service, you first need to register it. Because your solution has two services, you need to register both. Here is a code snippet written in the Activator class to register them:

IContextDataService dataService = new UserSessionService(); 
IValidateService validateService = new ValidateService(); 
// register the service
context.registerService(IContextDataService.class.getName(), dataService, new Hashtable());
context.registerService(IValidateService.class.getName(), validateService, new Hashtable());


The context object is the shared object across all the bundles within a single Equinox runtime. The context object here refers to the UserLoginServices bundle, which registers the two services with the Eclipse Equinox runtime. Because the OSGi framework allows bundles to expose services for other bundles to use, you expose the packages that implement the functionality of the above-mentioned services.

Here is a snapshot of the MANIFEST.MF of UserLoginServices bundle:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: CommonServices Plug-in
Bundle-SymbolicName: CommonServices
Bundle-Version: 1.0.0
Bundle-Activator: com.infy.setl.common.services.activator.Activator
Export-Package: com.infy.setl.common.services.genericserviceint,
 com.infy.setl.common.services.usermode;uses:="com.infy.setl.common.services.usermodelint",
 com.infy.setl.common.services.usermodelint,
 com.infy.setl.common.services.userserviceint;uses:="com.infy.setl.common.services.genericserviceint",
 com.infy.setl.common.services.validateservice,
 com.infy.setl.common.services.validateserviceint
Import-Package: org.osgi.framework;version="1.3.0",
 org.osgi.util.tracker;version="1.3.3"
Created-By: 1.5.0_06-b05 (Sun Microsystems Inc.)
Ant-Version: Apache Ant 1.7.1
Eclipse-LazyStart: true
Require-Bundle: org.eclipse.core.runtime


The packages exposed are the ones that contain POJO services. Another developer can import these packages individually or import the whole bundle in his or her application to make use of the services. Your solution itself uses the services of this bundle through UserLoginUI to validate and cache the credentials.

UserLoginUI

Here are the steps for creating a new UI plug-in.

  1. Click on File tab, select the New option, and then select the Other... option (see Figure 5).

  2. Select the Plug-in Development wizard and then Plug-in Project (see Figure 6).

  3. Use the default selection and click Next (see Figure 7).

  4. Click the Finish button and you have created a blank new plug-in (see Figure 8).

      Manifest and Activation of UI Bundle

      To import service bundle classes for use in the UserLoginUI bundle, specify the following in Manifest.MF:

      Manifest-Version: 1.0
      Bundle-ManifestVersion: 2
      Bundle-Name: CommonLoginAuth Plug-in
      Bundle-SymbolicName: CommonLoginAuth;singleton:=true
      Bundle-Version: 1.0.0
      Bundle-Activator: com.infy.setl.common.login.activator.Activator
      Require-Bundle: org.eclipse.ui,
       org.eclipse.core.runtime,
       org.eclipse.swt,
       org.eclipse.ui.workbench,
       org.eclipse.jface,
       CommonServices;bundle-version="1.0.0"
      Eclipse-LazyStart: true
      Export-Package: com.infy.setl.common.login.ui
      Bundle-ClassPath: .,
       org.eclipse.jface_3.3.2.M20080207-0800.jar
      Import-Package: com.infy.setl.common.services.usermode,
       com.infy.setl.common.services.usermodelint,
       com.infy.setl.common.services.userserviceint,
       com.infy.setl.common.services.validateservice,
       com.infy.setl.common.services.validateserviceint


      You export the IValidateService and IContextDataService classes to access the services.

      Next, you install and use the service imported above if the OSGI container has not done it for you (i.e., you start the services programmatically through code). You can do this by adding the code below in the Activator class or by designing a separate class. You need to have the service bundle started because the current bundle depends on the service bundle for validation and caching services.

      if(bundle!=null){
                  if(bundle.getState()==Bundle.ACTIVE){
                     context = bundle.getBundleContext();    
                     if(context!=null){
                        userServiceTracker = new ServiceTracker(context,IContextDataService.class.getName(), null);   
                        if(userServiceTracker!=null){
                           userServiceTracker.open();
                           // grab the service
                           userSessionService = (IUserSessionService)userServiceTracker.getService();
                           if(userSessionService!=null)
                              return userSessionService;
                        }
                     }
       
                  }
                  else {
                     try {
                        bundle.start();
                        context = bundle.getBundleContext();
                        if(context!=null){
                           //context.getBundle().start();
                           userServiceTracker = new ServiceTracker(context,IContextDataService.class.getName(), null);   
                           if(userServiceTracker!=null){
                              userServiceTracker.open();
                              userSessionService = (IUserSessionService)userServiceTracker.getService();
                              if(userSessionService!=null)
                                 return userSessionService;
                           }
                        }
                     }
                     catch (BundleException e) {
                        // TODO Auto-generated catch block
                        //e.printStackTrace();
                        javaLogger.log(Level.INFO, "Bundle Not Started",e);
                     }
                  }
               } }


    Plug-in Extensions

    Because this bundle uses two extension points (namely, Splash Handler and Preference Pages), you need to specify the same in plug-in.xml. The class node specifies the class name that extends the specific extension point. Below is a snapshot of plug-in.xml:

    <plugin>
     
       <extension
             point="org.eclipse.ui.splashHandlers">
          <splashHandler
                class="com.infy.setl.common.login.ui.LoginHandler"
                id="CollaborationCommonServices">
          </splashHandler>
          <splashHandlerProductBinding
                splashId="CollaborationCommonServices"
                productId="org.eclipse.platform.ide">
         </splashHandlerProductBinding>
       </extension>
      <extension
              point="org.eclipse.ui.preferencePages">
        <page
                  name="UserConfiguration"
                class="com.infy.setl.common.login.preferences.DeWConfigurationPreferencePage"
                id="DeW.User.Preferences">
       </page>
    </extension>
    </plugin>


    Splash Handler is an Eclipse extension point that produces a splash screen, a UI screen that appears at boot time. It is primarily provided to allow you to customize the look of the Eclipse boot time screen. As such, you can configure the screen further to have different animations, images, etc. The Splash Handler extension point definition in plug-in.xml contains a couple of extra nodes, namely splashId and productId. All the IDs defined in the plug-in.xml should always be unique because Equinox uses these IDs to uniquely identify a particular plug-in. During the bootstrapping process, you can customize the image location so that Eclipse picks up your defined location image for display rather than the default one.

    <extension
             point="org.eclipse.ui.splashHandlers">
          <splashHandler
                class="com.infy.setl.common.login.ui.LoginHandler"
                id="CollaborationCommonServices">
          </splashHandler>
          <splashHandlerProductBinding
                splashId="CollaborationCommonServices"
                productId="org.eclipse.platform.ide">
         </splashHandlerProductBinding>
       </extension>


    Next, you create a class extending AbstractSplashHandler. You override the init method to customize the way Eclipse loads.

    public void init(final Shell splash) {
          // Store the shell
          //javaLogger.entering("In Login Handler", "In init");
           javaLogger.log(Level.INFO, "In Init");
          super.init(splash);
          // Configure the shell layout
          configureUISplash();
          // Create UI
          initShell(splash);
          createUI();      
          // Create UI listeners
          addListeners();
          // Force the splash screen to layout
          splash.layout(true);
          // Keep the splash screen visible and prevent the RCP application from 
          // loading until the close button is clicked.
          
          closeWindow();
          
          
       }


    You then add a few lines of code to specify the image location for Eclipse to load.

    private void configureUISplash() {
          // Configure layout
          javaLogger.log(Level.INFO, "In UI Splash");
          FillLayout layout = new FillLayout(); 
          getSplash().setLayout(layout);
          // Force shell to inherit the splash background
          getSplash().setBackgroundMode(SWT.INHERIT_DEFAULT);
          try {
             getSplash().setBackgroundImage(getImage());
          } 
           catch (IOException e) {
             
                javaLogger.log(Level.WARNING,"Image not found",e);
             }
       }
     
       private Image getImage() throws IOException {
       
           Bundle bundle = Platform.getBundle(ICommonConstants.MY_PLUGIN_ID);
           Path path = new Path(ICommonConstants.RELATIVE_PATH_FOR_COLLABORATION_ECLIPSE_IMAGE);
           URL fileURL = FileLocator.find(bundle, path, null);
           InputStream in = fileURL.openStream();
            return  new Image(null,in);
          
       }


    Now you can add your desired number of widgets to get an output like Figure 9. In this example, you have added two textboxes and two buttons for the login screen.


    Figure 7. Two Textboxes and Two Buttons for the Login Screen

    Now you're ready to move on to preference pages. The Eclipse Preference Page is a common dialog box provided where the user can configure the way Eclipse behaves. The user can set or edit his/her credentials from this page. Below is the way to configure Preference Pages in the plug-in.xml.

    <extension
              point="org.eclipse.ui.preferencePages">
        <page
                  name="UserConfiguration"
                class="com.infy.setl.common.login.preferences.DeWConfigurationPreferencePage"
                id="DeW.User.Preferences">
       </page>
     
    </extension>


    You extend the PreferencePage class to add a new block to the Preference Page, and then you override the createContents method.

    protected Control createContents(final Composite parent)
        {
           javaLogger.log(Level.INFO,"In PreferencePage");
          super.noDefaultAndApplyButton();
           setParent(parent);
            Composite composite = new Composite(parent, SWT.NULL);
            composite.setSize(300,120);
            Group groupMainShell = new Group(composite, SWT.NULL);
              groupMainShell.setLayout(new GridLayout(2, true));
          groupMainShell.setText("Login Credentials");
          groupMainShell.setBounds(10, 10, 220,95);
          GridData data;
            labelUserName = new Label(groupMainShell, SWT.NONE);
            labelUserName.setText("UserName");
            data = new GridData(GridData.FILL_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            labelUserName.setLayoutData(data);
            labelUserName.setLocation(15, 30);
            labelUserName.pack();
            textBoxUserLogin = new Text(groupMainShell, SWT.BORDER);
            data = new GridData(GridData.FILL_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            textBoxUserLogin.setLayoutData(data);
            textBoxUserLogin.setLocation(80, 28);
            textBoxUserLogin.setSize(120, 20);
            labelPassword = new Label(groupMainShell, SWT.NONE);
            labelPassword.setText("Password");
            data = new GridData(GridData.FILL_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            labelPassword.setLayoutData(data);
            labelPassword.setLocation(15, 60);
            labelPassword.pack();
            textBoxPassword = new Text(groupMainShell, SWT.BORDER|SWT.PASSWORD);
            data = new GridData(GridData.FILL_HORIZONTAL);
            data.grabExcessHorizontalSpace = true;
            textBoxPassword.setLayoutData(data);
            textBoxPassword.setLocation(80, 58);
            textBoxPassword.setSize(120, 20);
            
          //  System.out.println("In Preference Page: "+ System.currentTimeMillis());
         
            userCreds =  new UserCredentials();
            userSessionService = DeWCommonAuthorisationUtil.getUserSessionObject();
            if(userSessionService!=null){
                if (userSessionService.get(System.getenv("USERNAME")) == null )
                 {
                    userCreds.setUserId(System.getenv("USERNAME"));
                    textBoxUserLogin.setText(userCreds.getUserId());
                 }
                 else {
                     textBoxUserLogin.setText(((IUserCredentials)(userSessionService.get(System.getenv("USERNAME")))).getUserId());
                     textBoxPassword.setText(((IUserCredentials)(userSessionService.get(System.getenv("USERNAME")))).getPassword());
                     setPassword(((IUserCredentials)(userSessionService.get(System.getenv("USERNAME")))).getPassword());
                     userCreds.setUserId(((IUserCredentials)(userSessionService.get(System.getenv("USERNAME")))).getUserId());
                      }
            }
            textBoxPassword.addModifyListener(new ModifyListener(){
             public void modifyText(ModifyEvent arg0) {
                // TODO Auto-generated method stub
                setPassword(textBoxPassword.getText());
             }} );
            return null;
        }


    The output of the above code would look like similar to Figure 10.


    Figure 10. Preference Page Login

    The final piece is a login pop-up window, which is a simple SWT window with user name and password text fields. It is an event-based pop-up window that collects the credentials from the user (see Figure 11).


    Figure 11. Preference Page Login

    Using the Login Solution Components

    The two bundles in the Login Solution (UserLoginUI and UserLoginServices) are modular components. In this section, you'll learn how to can plug these bundles into an application. Your solution provides services for validating and storing user credentials. As mentioned previously, to make use of the services, a developer can import packages individually or import the whole bundle in their applications. A snapshot of the UserLoginUI bundle's MANIFEST.MF is shown below in for the scenarios.

    Here is the MANIFEST.MF when whole bundle is imported:

    Require-Bundle: org.eclipse.ui,
     org.eclipse.core.runtime,
     org.eclipse.swt,
     org.eclipse.ui.workbench,
     org.eclipse.jface,
     CommonServices;bundle-version="1.0.0"


    Here it is for cases when specific packages are imported:

    Import-Package: com.infy.setl.common.services.activator,
     com.infy.setl.common.services.genericserviceint,
     com.infy.setl.common.services.usermode,
     com.infy.setl.common.services.usermodelint,
     com.infy.setl.common.services.userserviceint,
     com.infy.setl.common.services.validateservice,
     com.infy.setl.common.services.validateserviceint


    After this, you just add a few lines of code in your application and start using the the Common Services. Before using any service, you need an object of that particular service. The following snippet gets the service object of the cache services provided in the UserLoginServices bundle.

    if(bundle!=null){
                if(bundle.getState()==Bundle.ACTIVE){
                   context = bundle.getBundleContext();    
                   if(context!=null){
                      userServiceTracker = new ServiceTracker(context,IContextDataService.class.getName(), null);   
                      if(userServiceTracker!=null){
                         userServiceTracker.open();
                         // grab the service
                         userSessionService = (IUserSessionService)userServiceTracker.getService();
                         if(userSessionService!=null)
                            return userSessionService;
                      }
                   }
     
                }


    When you have the service object, you can invoke the services whenever you need them. Here is the invocation of the cache service.

    If( userSessionService != null)  { 
    userSessionService.save( creds.getUserId(), creds);
    }


    To use the UserLoginUI bundle, a user application needs to import the bundle or import the package exported by the UserLoginUI bundle. Click here to download the sample code.

    Further Possibilities with OSGi and Eclipse

    The Login Solution has a few other use cases to consider. For example, you could extend it for Eclipse RCP applications for which a developer needs to maintain multiple user credentials for multiple applications. In that use case, many different apps use this service bundle as middleware for login and session management. Wouldn't it be interesting if the whole solution was multi-tenant? Then what modifications would you need to make to meet the requirements? These are open questions for you to ponder as you explore the possibilities of OSGi.

    Code Download

  5. OSGI_Eclipse_Equinox

    About the Authors

    Prasad Siva Sai Choutupalli is a System Engineer for Infosys Technologies Ltd., India in SETLabs (Software Engineering and Technology Labs), the R&D division of the company. His experience is in building middleware and Eclipse-based applications.

    Manish Malhotra is a Technology Lead with SETLabs (Software Engineering and Technology Labs), the R&D division of Infosys Technologies Ltd. He works in the JEECoe group. Apart from designing and implementing Java- and Java EE-based applications, he has also worked on software engineering and semantic analysis research.

  6. Sitemap | Contact Us

    Thanks for your registration, follow us on our social networks to keep up-to-date