March 22, 2010

OSGI Tutorial and Running Example

« Definition Builder mit Java Beispiel | Main | OSGI Tutorial - Dynamic Usage of ServiceTracker »

Introduction

OSGI implements a dynamic component model for the Java platform. OSGI components or bundles defines its own context for packages and classes. This article describes the OSGI basics. Furthermore my intention is to clarify why OSGI is compared with SOA concepts and why the dynamic mechanism brings more flexibility in software architectures.

OSGI – Basic Concepts

Classloading – Difference between imported, exported and private packages

Before you starts developing OSGI you have to undestand the Java classloading concept. OSGI uses excessively the Java classloader mechanism. Mostly Java classloading is hierarchicaly used (see Java Classloader Delegation Model). OSGI realizes a network based classloader structure. Every OSGI bundle has it's own classloader. At this OSGI bundles seperate between imported, exported and private bundle packages. Classes of exported packages could be accessed from other running Bundles. Classes, which are declared in a bundle, but are required (e.g. referenced by import package.class), have to be imported.

Service registration

BundleActivator class

Before you develop a new Bundle you have to implement the org.osgi.framework.BundleActivator interface. At runtime the BundleActivator enables access to the BundleContext object out of your Bundle. The BundleContext contains all registered service references. For this you have to implement the lifecycle methods start(BundleContext ctx) and stop(BundleContext ctx).

 
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public final class Activator 
        implements BundleActivator {

    public void start(BundleContext context)
            throws Exception {
        //access to the bundle context on starting bundle
    }

    public void stop(BundleContext context)
            throws Exception {
        //clean up
    }
}

MANIFEST.MF - File

This file includes all possible OSGI instructions and will be interpreted by the OSGI runtime. For example Import-Package, Export-Package, declaration of Bundle-Activator and so on.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: FirstService
Bundle-SymbolicName: org.developers.blog.example.Service
Bundle-Version: 1.0.0
Bundle-Vendor: Rafael Sobek
Export-Package: org.developers.blog.sample.service
Import-Package: org.osgi.framework;version="1.3.0"

How to provide and consume services

OSGI bundles act as service providers and service consumers. The exchange of OSGI services is achieved by accessing BundleContext at your BundleActivator.

If you want to consume a service outside of your bundle, you have to get the ServiceReference by calling the BundleContext.getServiceReference(String serviceName) method. After that you can get the service object by calling BundleContext.getService(ServiceReference serviceReference) method.

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.ServiceReference;

public final class Activator
        implements BundleActivator {

    private ServiceRegistration myServiceRegistration;

    public void start(BundleContext context)
            throws Exception {
        
        ServiceReference referenceOfExternalServiceObject =
                context.getServiceReference(ExternalService.class.getName());

        //if ExternalService object already registered in OSGI runtime
        if (referenceOfExternalServiceObject != null) {
            ExternalService externalService =
                  (ExternalService) context.getService(referenceOfExternalServiceObject);
            MyService myServiceObject = new MyService();
            //adds the bundle service object to the bundle context
            this.myServiceRegistration = context.registerService(MyService.class.getName(), myServiceObject, null);
        }
    }

    public void stop(BundleContext context)
            throws Exception {
        //remove bundles service object from bundle context
        this.myServiceRegistration.unregister();
    }
}

OSGI example

This example doesn't use any dependency resolution mechanism how Spring DM, Declarative Services or IPojo. It contains two bundles. The persistence-service-bundle registers a PersistenceService with an embedded Derby database to the BundleContext. If the persistence-service-bundle is started in OSGI container, the BundleActivator will instance Derby DB. The customer-web-overview-bundle uses the PersistenceService to get the list of customers in database and generates a HTML table. Additionaly it's implementing a Servlet. The BundleActivator of this bundle uses the Whiteboard pattern to add this Servlet event oriented to the HTTPService.
Used concepts:

  • Service providing by Whiteboard Pattern
  • Service consuming by standard OSGI Activator
Used containers:
  • Pax Runner Provisioning System
  • Felix OSGI Container

Download and Deployment
You can download full example here. To run this Maven project in OSGI container you have to execute mvn clean install pax:provision. After all bundles was started you can show the result at your browser. Check out the following URL http://localhost:8080/customerlist.

Persistence-Service-Bundle

DerbyManager - Manages Connection to Database

package org.developers.blog.osgi.basic.persistence.internal;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
 *
 * @author Rafael Sobek
 */
public class DerbyManager {
    private final static String DERBY_DRIVER =
            "org.apache.derby.jdbc.EmbeddedDriver";
    private final static String DATABASE_NAME = "persistenceDB";

    private Connection connection = null;

    public DerbyManager() throws Exception {
        init();
    }

    public void init() throws Exception {
        Class.forName(DERBY_DRIVER).newInstance();
        String property = "java.io.tmpdir";
        String tempDir = System.getProperty(property);
        String jdbcURL = "jdbc:derby:" + tempDir + "/" + DATABASE_NAME + ";create=true";
        System.out.println(jdbcURL);
        connection = DriverManager.getConnection(jdbcURL);
        generateDummyCustomers();
    }

    public Connection getConnection() {
        return this.connection;
    }

    public void destroy() throws Exception {
        connection.close();
        DriverManager.getConnection("jdbc:derby:;shutdown=true");
    }

    public void generateDummyCustomers() throws Exception {
        Statement stmt = null;
        try {
            stmt = connection.createStatement();
            stmt.executeUpdate("DROP TABLE customer");
            stmt.close();
        } catch (Exception ex) {}

        String createTable = "CREATE TABLE customer (" +
                "id INT NOT NULL GENERATED ALWAYS AS IDENTITY," +
                "firstName VARCHAR(128)," +
                "secondName VARCHAR(128)," +
                "address VARCHAR(128)," +
                "city VARCHAR(128)," +
                "country VARCHAR(128)," +
                "email VARCHAR(128))";

        stmt = connection.createStatement();
        stmt.executeUpdate(createTable);
        stmt.close();
        
        String insert = "INSERT INTO customer " +
                "(firstName, secondName, address, city, country, email) VALUES " +
                "('Rafael','Sobek','Kaiseralle','Karlsruhe','Germany','info@developers-blog.org')";

        stmt = connection.createStatement();
        stmt.executeUpdate(insert);
        stmt.close();
    }
}

Persistence Service - Reads Database and Generates Customer Object List

package org.developers.blog.osgi.basic.persistence;

import org.developers.blog.osgi.basic.persistence.vo.Customer;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.developers.blog.osgi.basic.persistence.internal.DerbyManager;

public class PersistenceService
{
    private DerbyManager derbyManager;

    public PersistenceService() throws Exception {
        this.derbyManager = new DerbyManager();
    }

    public List getCustomers() throws PersistenceException {
        Statement stmt = null;
        ResultSet rs = null;

        List customers = new ArrayList();

        try {
            Connection connection = derbyManager.getConnection();
            stmt = connection.createStatement();
            rs = stmt.executeQuery("SELECT * FROM CUSTOMER");
            while (rs.next()) {
                customers.add(
                    new Customer(
                        rs.getString("firstName"),
                        rs.getString("secondName"),
                        rs.getString("address"),
                        rs.getString("city"),
                        rs.getString("country"),
                        rs.getString("email")));
            }
        } catch (Exception ex) {
            throw new PersistenceException("DB query failed", ex);
        } finally {
            try {
                rs.close();
            } catch (SQLException ex) {
                throw new PersistenceException("DB closing failed", ex);
            }
            try {
                stmt.close();
            } catch (SQLException ex) {
                throw new PersistenceException("DB closing failed", ex);
            }
        }

        return customers;
    }
    
}

Activator - Adds PersistenceService object to BundleContext

package org.developers.blog.osgi.basic.persistence;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

public class Activator implements BundleActivator {
    private ServiceRegistration serviceRegistration;

    public void start(BundleContext bundleContext) throws Exception {
        PersistenceService persistenceService =
                new PersistenceService();
        serviceRegistration =
                bundleContext.registerService(
                    PersistenceService.class.getName(), persistenceService, null
                );
        
    }

    public void stop(BundleContext bundleContext) throws Exception {
        serviceRegistration.unregister();
    }
}

Customer-Web-Overview-Bundle

CustomerTableServlet - List all customers in HTML table

It uses for listing customer the PersistenceService object of the Persistence-Serivce-Bundle. Additionaly this Servlet contains helper methods to build up a HTML table.
package org.developers.blog.osgi.basic.web.whiteboard;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.Writer;
import org.developers.blog.osgi.basic.persistence.PersistenceException;
import org.developers.blog.osgi.basic.persistence.PersistenceService;
import org.developers.blog.osgi.basic.persistence.vo.Customer;

public class CustomerTableServlet
        extends HttpServlet {

    private PersistenceService persistenceService;

    public void setPersistenceService(PersistenceService persistenceService) {
        this.persistenceService = persistenceService;
    }

    @Override
    public void init(ServletConfig config)
            throws ServletException {
        doLog("Init config [" + config + "]");
        super.init(config);
    }

    @Override
    public void destroy() {
        doLog("Destroyed servlet");
        super.destroy();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        Writer writer = resp.getWriter();
        writeTop(writer);
        String header = wrapRow(
                wrapCell("First Name")
                + wrapCell("Second Name")
                + wrapCell("Address")
                + wrapCell("City")
                + wrapCell("Country")
                + wrapCell("Email"));
        try {
            String content = null;
            for (Customer customer : persistenceService.getCustomers()) {
                content = wrapRow(
                        wrapCell(customer.getFirstName())
                        + wrapCell(customer.getSecondName())
                        + wrapCell(customer.getAddress())
                        + wrapCell(customer.getCity())
                        + wrapCell(customer.getCountry())
                        + wrapCell(customer.getEmail()));
            }
            String table = wrapTable(header + content);
            writer.write(table);
        } catch (PersistenceException ex) {
            ex.printStackTrace();
            throw new ServletException(ex);
        }

        writeBottom(writer);

    }

    private void writeTop(Writer writer) throws IOException {
        writer.write(
                "<html>"
                + " <head>"
                + "  <title>OSGI Basic Web Example</title>"
                + " </head>"
                + " <body>");
    }

    private void writeBottom(Writer writer) throws IOException {
        writer.write(
                " </body>"
                + "</html>");
    }

    private String wrapTable(String value) throws IOException {
        return "<table border='1'>" + value + "</table>";
    }

    private String wrapCell(String value) throws IOException {
        return "<td>" + value + "</td>";
    }

    private String wrapRow(String value) throws IOException {
        return "<tr>" + value + "</tr>";
    }

    private void doLog(String message) {
        System.out.println("[" + this.getClass().getName() + "] " + message);
    }
}

Activator - Adds the CustomerTableServlet to BundleContext

package org.developers.blog.osgi.basic.web.whiteboard;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import javax.servlet.Servlet;
import java.util.Hashtable;
import org.developers.blog.osgi.basic.persistence.PersistenceService;
import org.osgi.framework.ServiceReference;

public final class Activator
        implements BundleActivator {

    private ServiceRegistration serviceRegistration;

    public void start(BundleContext context)
            throws Exception {
        ServiceReference sRefPersistenceService =
                context.getServiceReference(PersistenceService.class.getName());

        //if PersistenceService exists in OSGI runtime
        if (sRefPersistenceService != null) {
            PersistenceService persistenceService =
                    (PersistenceService) context.getService(sRefPersistenceService);
            CustomerTableServlet tableServlet = new CustomerTableServlet();
            tableServlet.setPersistenceService(persistenceService);
            //use whiteboard pattern to register servlets
            Hashtable props = new Hashtable();
            props.put("alias", "/customerlist");
            this.serviceRegistration = context.registerService(Servlet.class.getName(), tableServlet, props);
        }
    }

    public void stop(BundleContext context)
            throws Exception {
        this.serviceRegistration.unregister();
    }
}

Regards
Rafael Sobek

Technorati Tags:

Posted by rafael.sobek at 9:39 AM in OSGI

 

[Trackback URL for this entry]

Comment: Gyowanny at Di, 23 Mrz 4:17 PM

Nice Article.
You could show how to set things up in the Servlet Container in order to have an OSGi application running.

Thank you

Pingback: links for 2010-03-23 &laquo; edansys at Mi, 24 Mrz 12:12 AM

OSGI Tutorial and Running Example
Tutorial and Running Example (tags:

Pingback: Blog bookmarks 03/24/2010 &laquo; My Diigo bookmarks at Mi, 24 Mrz 4:30 AM

OSGI Tutorial and Running Example
gradle OSGI Tutorial and Running

Your comment:

(not displayed)
 
 
 

Live Comment Preview:

 
« March »
SunMonTueWedThuFriSat
 123456
78910111213
14151617181920
21222324252627
28293031