Пример использования OSGi


Пример использования OSGi

Немного о технологии OSGi:

Технология OSGi представляет собой набор спецификаций, которые определяют модель для динамического связывания компонентов на языке Java между собой.

Разработка ведется под OSGi Alliance (более подробная информация доступна тут

Спецификации позволяют разбивать приложение на компоненты (сервисы), которые легко использовать повторно и которые связываются динамически. Сервисом может стать любой Java объект, зарегистрированный в OSGi. Для для классов, которые обращаются к нему предоставляется лишь интерфейс (реализация скрывается).

На странице описаны основные достоинства OSGi.

Основные компоненты OSGi

Bundle (каталог или JAR архив) содержит java-классы и другие ресурсы, которые вместе могут реализовывать некие функции, а также предоставлять сервисы и пакеты другим bundle.
В bundle входят следующие компоненты:
  • Классы и ресурсы в которых реализована функциональность
  • Класс активатор (может отсутствовать)
  • Файл  META-INF/MANIFEST.MF – в нем определяются параметры bundle.

Контейнер - приложение, которое предназначено для запуска и управления bundles. В настоящий момент,  реализованы следующий OSGi контейнеры: Virgo, Equinox, ApacheFelix, Knopflerfish

Жизненый цикл bundle 

Bundle может находится в одном из состояний,  описание которых приведено ниже:
  • INSTALLED - успешно установлен в контейнер
  • RESOLVED - разрешены все зависимости, доступны все Java-классы и bundle, от которых он зависит. Из этого состояния возможен запуск bundle
  • STARTING – запуск, метод BundleActivator.start() выполняется и пока не вернул управление
  • ACTIVE - bundle успешно запустился. Метод BundleActivator.start() вернул управление
  • STOPPING – процесс остановки, метод BundleActivator.stop() вызван, но пока не вернул управление.
  • UNINSTALLED – удален из контейнера, соответственно он не может переходить в другие состояния. Жизненный цикл бандла завершен. 
Переходы между состояниями (не мое сперто из просторов интернета)



Пример использования OSGi: TimeServer

TimeServer демонстрирует следующие случаи использования OSGi:
  • Публикация библиотеки как OSGi контейнера
  • Регистрация OSGi сервиса (использования BundleActivator)
  • Получение ссылки на OSGi сервис (используя ServiceListiner)
  • Работа с OSGi консолью  
Для демонстрации возможностей OSGi было разработано приложение TimeServer, которое показывает текущее время в аэропорту по его коду.

Приложение разбито на отдельные bundle, для того, чтобы можно было вести независимую разработку и показать возможности независимого изменение компонентов системы.

Компоненты (bundle) примера:
  • AirportInfoService - библиотека для определения информации об аэропорте по его коду
  • TimeServiceInterface - bundle, в котором находятся интерфейсы
  • TimeService - сервис для определения по коду аэропорта времени в нем
  • WSTimeServer- сервер, принимающий запросы по WebServices и возвращающий время в городе.
Между компонентами существуют следующие зависимости:
TimeService - AirportInfoService: TimeService вызывает методы класса AirportInfoProvider, который находится в AirportInfoServis, при этом происходит вызов статического метода. Ссылка на класса получается через простой импорт.
WSTimeServer - TimeService: WSTimeServer является RemoteFacade для TimeService, предоставляя возможность вызывать методы через WebServices.
TimeService регистрирует OSGi сервис используя интерфейс TimeService (берется из bundle TimiServiceInterface), в свою очередь WSTimeServer получает на него ссылку через BundleContext.

AirportInfoService

В данном bundle находятся классы для определения параметров аэропорта по его коду. Bundle не имеет активатора, классы доступны как простые Java классы. Ниже приведены листинги классов. 

Airport

package lv.nixx.osgi.sample.countryservice;

import java.util.TimeZone;

public class Airport {
   
    private TimeZone timeZone;
    private String code;
    private String name;

    private Airport(TimeZone timeZone, String code, String name) {
        this.timeZone = timeZone;
        this.code = code;
        this.name = name;
    }
   
    static Airport create(TimeZone timeZone, String code, String name) {
        return new Airport(timeZone, code, name);
    }
   
    public TimeZone getTimeZone() {
        return timeZone;
    }
   
    public String getCode() {
        return code;
    }
   
    public String getName() {
        return name;
    }
   
}

AirportInfoProvider

package lv.nixx.osgi.sample.countryservice;

import java.util.*;

public class AirportInfoProvider {
   
    static Map<String, Airport> ai 

           = new HashMap<String,Airport>();

    static {
        ai.put("RIX",
                Airport.create(TimeZone.getTimeZone("GMT+3"),
                "RIX", "Riga Airport"));
        ai.put("KBP",
                Airport.create(TimeZone.getTimeZone("GMT+3"),
                "KBP", "Kyiv - Borispol Airport"));
        ai.put("SVO",
                Airport.create(TimeZone.getTimeZone("GMT+3"),
                "SVO",
                "Moscow Sheremetyevo Airport"));
        ai.put("CDG",
                Airport.create(TimeZone.getTimeZone("GMT+2"),
                "CDG",
                "Paris-Charles de Gaulle Airport"));
        ai.put("LGW",
                Airport.create(TimeZone.getTimeZone("GMT+0"),
                "LGW",
                "London Gatwick Airport"));
    }
   
    public static Airport getAirportByCode(String code) 

        throws Exception {
        

        if (!ai.containsKey(code)) {
            throw new Exception(

              "Airport with code [" + code + "] not found");
        }
        return ai.get(code);
    }
}

Содержимое файла MANIFEST.MF показано ниже:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: AirportInfoService
Bundle-SymbolicName: AirportInfoService
Bundle-Version: 1.0.1.qualifier
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: lv.nixx.osgi.sample.countryservice

Данный bundle не имеем активатора, соответственно, обращение к классам возможно только как к простым Java классам, а не как к OSGi сервису. Другим bundle предоставляются классы из пакета lv.nixx.osgi.sample.countryservice.

TimeServiceInterface

В данный bundle вынесен интерфейс TimeService, это необходимо, чтобы отделить реализацию от интерфейса. В этом случае, будет возможно обновление TimeService bundle на "лету", при помощи update, не затрагивая WSTimeService.

TimeService

Код интерфейса TimeService показан ниже:

package lv.nixx.osgi.sample.serviceinterface;

public interface TimeService {
    public String getTimeByAirportCode(String code) 
           throws Exception;
}

MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TimeServiceInterface
Bundle-SymbolicName: TimeServiceInterface
Bundle-Version: 1.0.0.qualifier
Export-Package: lv.nixx.osgi.sample.serviceinterface



Другим bundle предоставляются классы из пакета: lv.nixx.osgi.sample.serviceinterface

TimeService

Представляет собой сервис, который возвращает информацию о времени в аэропорту по его коду. Для того, чтобы сервис, был доступен для других bundle как OSGi сервис необходимо в нем необходимо реализовать класс активатор, код которого показан ниже:

TimeServiceActivator

package lv.nixx.osgi.sample;

import java.util.*;

import lv.nixx.osgi.sample.service.*;
import lv.nixx.osgi.sample.serviceinterface.TimeService;

import org.osgi.framework.*;

public class TimeServiceActivator implements BundleActivator {

    public ServiceRegistration serviceRegistration;

    @Override
    public void start(BundleContext bundleContext) 

        throws Exception {
        

        Dictionary<String, String> properties
         = new Hashtable<String, String>();
        properties.put("description",
            "TimeService, provide service " + 

            "to get time in airport by code");
      
        serviceRegistration = bundleContext.registerService(
                TimeService.class.getName(),
                new TimeServiceImpl(),
                properties);

        System.out.println("'TimeService' bundle started");
    }

    @Override
    public void stop(BundleContext bundleContext) 

       throws Exception {
        serviceRegistration.unregister();
        System.out.println("'TimeService' bundle stoped");
    }
}
Реализуя BundleActivator у разработчика есть возможность управлять жизненным циклом bundle в OSGi контейнере (один bundle может содержать только один активатор), При этом, в одном bundle может быть зарегистрировано любое количество сервисов.

 Для того, чтобы контейнер начал воспринимать класс как активатор, должны быть выполнены следующие условия:
  • класс должен реализовывать интерфейс BundleActivator. У разработчика есть возможность определить действия, которые будут происходить при старте bundle (метод start) и при остановки bundle (метод stop).
  • В файле MANIFEST.MF должен быть прописан класс — активатор: 
    Bundle-Activator: lv.nixx.osgi.sample.TimeServiceActivator

TimeServiceImpl

Код класса TimeServiceImpl, в котором реализована функциональность по определению времени в аэропорту показан ниже.
package lv.nixx.osgi.sample.service;

import java.text.*;
import java.util.*;

import lv.nixx.osgi.sample.countryservice.*;
import lv.nixx.osgi.sample.serviceinterface.TimeService;


public class TimeServiceImpl implements TimeService {

    public String getTimeByAirportCode(String code) 

        throws Exception {
        

        Airport airport =
             AirportInfoProvider.getAirportByCode(code);

        DateFormat dateFormat = 

            new SimpleDateFormat("z yyyy.MM.dd HH.mm.sss");
        dateFormat.setTimeZone(airport.getTimeZone());

        String formatedDateTime = dateFormat.format(new Date());

        return buildResponseString(airport, formatedDateTime);
    }
   
    private String buildResponseString(

        Airport airport, String formatedDateTime) {
        

        StringBuilder sb = new StringBuilder();
        sb.append("time at airport, code:");
        sb.append(airport.getCode());
        sb.append("(");
        sb.append(airport.getName());
        sb.append(") [");
        sb.append(formatedDateTime);
        sb.append("]");
       
        return sb.toString();
    }

}

MANIFEST.MF


В файле,  MANIFEST.MF определяются параметры bundle
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TimeService
Bundle-SymbolicName: TimeService
Bundle-Version: 1.0.1.qualifier
Bundle-Activator: lv.nixx.osgi.sample.TimeServiceActivator
Import-Package: lv.nixx.osgi.sample.countryservice;version="[0.0.0,2.0.0)",
org.osgi.framework;version="1.3.0", lv.nixx.osgi.sample.serviceinterface
Расмотрим значения параметров:
  • Import-Package: определяют пакеты, в которых находятся классы необходимые для работы, видим, что необходимы классы из пакета lv.nixx.osgi.sample.countryservice и lv.nixx.osgi.sample.serviceinterface. При этом, обращаем внимание, что нет явного указания на имя bundle где находится пакет, что позволяет использовать различные реализации.
  •  Bundle-Activator: при старте bundle запускается класс:
    lv.nixx.osgi.sample.TimeServiceActivator

WSTimeServer 

Данный bundle представляет собой RemoteFacade для TimeService, вынесение его в отдельный bundle понадобилось для того, чтобы можно было изменять cервис TimeService не затрагивая данного объекта.

MANIFEST.MF

Рассмотрим MANIFEST.MF данного сервиса:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: WSTimeServer
Bundle-SymbolicName: WSTimeServer
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: lv.nixx.osgi.sample.timeserver.WSTimeServerStarter
Import-Package: lv.nixx.osgi.sample.serviceinterface;version="[1.0.0,2.0.0)",
 org.osgi.framework;version="1.3.0"



Данный bundle активируется при помощи класса WSTimeServerStarted, для его работы нужны классы из пакета lv.nixx.osgi.sample.serviceinterface, другим сервисам никакие классы не предоставляются.

WSTimeServerStarter

Ниже показан код класса активатора, при помощи которого сервис запускается как WebService и происходит получение ссылки на TimeService.

package lv.nixx.osgi.sample.timeserver;
import java.util.*;
import lv.nixx.osgi.sample.serviceinterface.TimeService;
import lv.nixx.osgi.sample.timeserver.service.*;
import org.osgi.framework.*;
public class WSTimeServerStarter implements BundleActivator {
    private ServiceRegistration registration;
    private WSTimeServerImpl wsTimeServerImpl = null;
   
    public void start(final BundleContext context)

        throws Exception {
       
        wsTimeServerImpl = new WSTimeServerImpl();

        Dictionary<String, String> properties 

          = new Hashtable<String, String>();
        String hostAddress = "http://localhost:9001/TimeServer";
       
        properties.put("service.exported.interfaces", "*");
        properties.put("service.exported.configs", "pojo");
        properties.put("org.apache.cxf.ws.address", hostAddress);
        TimeServiceListener listener = new TimeServiceListener();
        String serviceFilter = "(objectclass=" +
                                TimeService.class.getName() +
                                ")";
       
        context.addServiceListener(listener, serviceFilter);

        // possible situation when TimeService already had
        // been registered, the listener above would not
        // be called until the TimeService were restarted.
        // We must manually fire this event in listener
        fireEvent_REGISTERED(context, listener, serviceFilter);
        registration =
            context.registerService(WSTimeServer.class.getName(),
                                               wsTimeServerImpl,
                                               properties);
        System.out.println("TimeServer registration success, " +
                            "ws.address [" + hostAddress + "]");
    }
    private void fireEvent_REGISTERED(BundleContext context,
            ServiceListener serviceListiner, String filter)
            throws InvalidSyntaxException {
      ServiceReference[] lst 

         = context.getServiceReferences(null, filter);
        for (int i = 0; lst != null && i < lst.length; i++) {
            final ServiceReference sr = lst[i];
            serviceListiner.serviceChanged(
                    new ServiceEvent(

                        ServiceEvent.REGISTERED, sr));
        }
    }
    public void stop(BundleContext context) throws Exception {
        registration.unregister();
        wsTimeServerImpl = null;
        System.out.println("WS TimeServer unregistered");
    }
    private class TimeServiceListener implements ServiceListener{
      public void serviceChanged(ServiceEvent serviceEvent) {
        ServiceReference sr = serviceEvent.getServiceReference();
            final int eventType = serviceEvent.getType();
            switch (eventType) {
                case ServiceEvent.UNREGISTERING: {
                    System.out.println("Event 'UNREGISTERING'("
                            + eventType + ") for service " +
                            "["+ sr+ "] is  fired");
                }
                break;
                case ServiceEvent.REGISTERED: {
                    System.out.println("Event 'REGISTERED' ("
                            + eventType + ") for service " +
                            "["+ sr+ "] is  fired");
                   
                    Bundle bundle = sr.getBundle();
                    TimeService timeService =
                        (TimeService)bundle
                        .getBundleContext()
                        .getService(sr);
                    wsTimeServerImpl.setTimeService(timeService);
                }
                break;
            default:
                break;
            }
        }
    };
}

В классе находится внутрений класс TimeServiceListiner (реализует интерфейс ServiceListiner), данный класс является слушателем для сервиса TimeService. При изменении состояния сервиса вызывается метод serviceChanged, при событии REGISTERED, происходит получение ссылки на сервис TimeService и ее передача экземпляру класса WSServiceImpl для вызова методов. Обратим внимание на то, что мы принудительно инициируем событие REGISTERED при помощи метода fireEvent_REGISTERED это необходимо для того, чтобы небыло зависимости от порядка запуска bundles.  Существует вероятность того, что TimeService запустится прежде чем WSTimeService и в этом случае, событие REGISTERED не будет инициировано.
Использовании listiner обеспечивает возможность изменения TimeService "на лету", при изменении достаточно лишь вызвать update в консоли сервера, после чего, WSTimeService начнет работать с новой версией.

WSTimeService 

Интерфейс WSTimeService необходим для работы сервера WebService, именно этот интерфейс предоставляется клиентам, код интерфейса показан ниже.

package lv.nixx.osgi.sample.timeserver.service;

public interface WSTimeServer {
    public String getTime(String airportCode) throws Exception;
}

WSTimeServiceImpl

В классе WSTimeServiceImpl происходит реализация интрефейса WSTimeService. Реализация тривиальна, в методе getTime() происходит делигирование вызова в TimeService, код показан ниже.

package lv.nixx.osgi.sample.timeserver.service;

import lv.nixx.osgi.sample.serviceinterface.TimeService;

public class WSTimeServerImpl implements WSTimeServer {

    private TimeService timeService = null;
   
    public void setTimeService(TimeService timeService){
        this.timeService = timeService;
    }
   
    @Override
    public String getTime(String airportCode) throws Exception {
        if (timeService == null) {
            throw new IllegalStateException(
                    "TimeService not available in this moment");
        }
        return timeService.getTimeByAirportCode(airportCode);
    }

}

Тестовый клиент (WS Client)

Для тестирования работы приложения был написан тестове клиенты, который вызывает WSTimeServer через webservices. Один тестовый клиент делает одиночный запрос на сервер, другой отправляет запросы на сервер в бесконечном цикле (между запросам поток засыпает на 500 мс).
Генерация Java кода необходимого для обращению к WebServer выполнена при помощи запуска класса org.apache.axis.wsdl.WSDL2Java со следующими параметрами: 
-o ./generated -p lv.nixx.osgi.sample.ws http://localhost:9001/TimeServer?wsdl
В момент запуска, сервер должен быть запущен, поскольку WSDL берется с сервера.

GenericTimeServerTester

Общая фунциональность вынесена в базовый класс GenericTimeServerTester, код которого показан ниже.

package lv.nixx.osgi.sample.timeservice.client;

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import org.apache.log4j.Logger;

import lv.nixx.osgi.sample.ws.Exception;
import lv.nixx.osgi.sample.ws.WSTimeServerLocator;
import lv.nixx.osgi.sample.ws.WSTimeServerPortType;

public abstract class GenericTimeServerTester {

    protected Logger logger = Logger.getLogger(this.getClass());
   
    protected WSTimeServerPortType getReferenceWSServer(String host)
        throws MalformedURLException, ServiceException {
       
        WSTimeServerLocator proxy = new WSTimeServerLocator();
        final URL timeServerURL = new URL(host);
        final WSTimeServerPortType timeServerServicePort =
            proxy.getWSTimeServerPort(timeServerURL);
        return timeServerServicePort;
    }

    protected void getTimeInRandomAirport(
        WSTimeServerPortType timeServerServicePort)
        throws RemoteException {
       
       String[] codeArray = 

          new String[]{"RIX","KBP","SVO","CDG","LGW"};
       final int randomPosition = 

         (int)(Math.random() * codeArray.length);
        logger.debug("TimeServer response [" +
              timeServerServicePort.getTime(

              codeArray[randomPosition]) +
              "]");
    }
   
    protected abstract void executeTest() throws Exception;

}

SingleRequestCaller

Код класса для единичного обращения к серверу показан ниже:

package lv.nixx.osgi.sample.timeservice.client;

import java.net.MalformedURLException;

import javax.xml.rpc.ServiceException;

import lv.nixx.osgi.sample.ws.*;
import lv.nixx.osgi.sample.ws.Exception;

public class SingleRequestCaller 

  extends GenericTimeServerTester {

    public static void main(String[] args) throws Exception {
        SingleRequestCaller tester = new SingleRequestCaller();
        tester.executeTest();
    }

    @Override
    protected void executeTest() throws Exception {
        try {
            final WSTimeServerPortType timeServerServicePort =
            getReferenceWSServer(

                "http://localhost:9001/TimeServer");
            try {
              this.getTimeInRandomAirport(timeServerServicePort);
            } catch (java.lang.Throwable ex) {
                logger.error(ex, ex);
            }            
        } catch (MalformedURLException e) {
            final Exception exception = new Exception();
            exception.initCause(e);
            throw exception;
        } catch (ServiceException e) {
            final Exception exception = new Exception();
            exception.initCause(e);
            throw exception;
        }
    }

} 

InfiniteServerCaller

Для обращения к серверу в бесконечном цикле разработан класс InfiniteServerCaller код которого показан ниже.
package lv.nixx.osgi.sample.timeservice.client;

import java.net.MalformedURLException;

import javax.xml.rpc.ServiceException;

import lv.nixx.osgi.sample.ws.*;
import lv.nixx.osgi.sample.ws.Exception;

public class InfiniteServerCaller 

   extends GenericTimeServerTester {

    public static void main(String[] args) throws Exception {
        InfiniteServerCaller tester = new InfiniteServerCaller();
        tester.executeTest();
    }

    @Override
    protected void executeTest() throws Exception {
        try {
            WSTimeServerPortType timeServerServicePort =
                getReferenceWSServer(
                  "http://localhost:9001/TimeServer");
   
            Object lock = new Object();
            while (true) {
            try {
                 getTimeInRandomAirport(
                    timeServerServicePort);
    
                    synchronized (lock) {
                        try {
                            lock.wait(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (java.lang.Throwable ex) {
                    logger.error(ex, ex);
                }
            }
        } catch (MalformedURLException e) {
            final Exception exception = new Exception();
            exception.initCause(e);
            throw exception;
        } catch (ServiceException e) {
            final Exception exception = new Exception();
            exception.initCause(e);
            throw exception;
        }
    }

}
 

Основные консольные команды для работы с контейнером (Equinox)


Отображение статусов Bundle


ss - отображение (краткое) статуса установленных bundles

Пример вывода команды:
id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
2    ACTIVE      osgi.cmpn_4.2.0.200908310645
3    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
34    ACTIVE      AirportInfoService_1.0.4.qualifier
42    ACTIVE      TimeService_1.0.2.qualifier
43    ACTIVE      WSTimeServer_1.0.0.qualifier

headers - заголовки конкретного bundle

Команда выводить заголовки конкретного bundle (по id), ниже показан вывод для TimeService bundle.

osgi> headers 42
Bundle headers:
 Bundle-Activator = lv.nixx.osgi.sample.TimeServiceActivator
 Bundle-ManifestVersion = 2
 Bundle-Name = TimeService
 Bundle-SymbolicName = TimeService
 Bundle-Version = 1.0.2.qualifier
 Export-Package = lv.nixx.osgi.sample.serviceinterface;version="1.0.1"
 Import-Package = lv.nixx.osgi.sample.countryservice;version="[0.0.0,2.0.0)",org.osgi.framework;version="1.3.0"
 Manifest-Version = 1.0

services - отображение зарегистрированных сервисов

Команда отображает сервисы зарегистрированные bundle, в параметрах можно передать фильтр, в которым указать критерии сервисов, которые выводить.

Пример выполнения команды services (objectClass=lv.nixx.*)

osgi> services (objectClass=lv.nixx.*)
{lv.nixx.osgi.sample.serviceinterface.TimeService}=
  {description=TimeService, provide service to get time in airport by code, service.id=28}
  Registered by bundle: TimeService_1.0.2.qualifier [42]
  Bundles using service:
    WSTimeServer_1.0.0.qualifier [43]

{lv.nixx.osgi.sample.timeserver.service.WSTimeServer}={org.apache.cxf.ws.address=http://localhost:9001/TimeServer, service.exported.configs=pojo, service.exported.interfaces=*, service.id=42}
  Registered by bundle: WSTimeServer_1.0.0.qualifier [43]
  Bundles using service:
    cxf-dosgi-ri-singlebundle-distribution_1.3.0 [3]

Управление жизненым циклом bundle

install - установка bundle в контейнере (параметром команды является URL).

uninstall - удаление bundle из контейнера

start - запуск bundle

stop - остановка bundle

refresh - обновление пакетов в bundle

update - обновление bundle



Тестовые сценарии работы приложения

Запуск приложения вызов сервера

Тестовый сценарий:
  • запускаем контейнер с установленными bundle
  • проверяем состояние bundle (команда ss)
  • выполняем запрос от клиента
Ожидаемый результат: запрос от сервера вернул время в аэропорту
После запуска приложения выполняем команду ss, получаем список установленных bundle с их состоянием

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
2    ACTIVE      osgi.cmpn_4.2.0.200908310645
48    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
49    ACTIVE      WSTimeServer_1.0.0.qualifier
51    ACTIVE      TimeService_1.0.3.qualifier
52    ACTIVE      TimeServiceInterface_1.0.0.qualifier
53    ACTIVE      AirportInfoService_1.0.4.qualifier


Лог на стороне клиента:
27/06/12 15:14:08,633 [main] TimeServer response [time at airport, code:SVO(Moscow Sheremetyevo Airport) [GMT+03:00 2012.06.27 15.14.008]]

Обновление TimeService bundle

Тестовый сценарий

  1. запускаем контейнер с установленными bundle
  2. проверяем состояние bundle (команда ss)
  3. выполняем запрос от клиента
  4. изменим строку возвращаемую TimeServiceImpl
  5. выполняем обновление TimeService bundle (команда update)
  6. выполняем запрос от клиента
  7. проверяем состояние bundle (команда ss)
Ожидаемый результат: второй запрос от клиента получил измененную строку  

Выполнение тестового сценария

  1. запускаем контейнер с установленными bundle
  2. проверяем состояние bundle (команда ss)
    Framework is launched.

    id    State       Bundle
    0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
    2    ACTIVE      osgi.cmpn_4.2.0.200908310645
    48    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
    49    ACTIVE      WSTimeServer_1.0.0.qualifier
    51    ACTIVE      TimeService_1.0.3.qualifier
    52    ACTIVE      TimeServiceInterface_1.0.0.qualifier
    53    ACTIVE      AirportInfoService_1.0.4.qualifier 


    Обращаем внимаение на версию TimeService - 1.0.3
  3. выполняем запрос от клиента
    27/06/12 15:26:59,297 [main] TimeServer response [time at airport, code:KBP(Kyiv - Borispol Airport) [GMT+03:00 2012.06.27 15.26.059]]
  4. изменим строку возвращаемую TimeServiceImpl
    Теперь должна возвращатся строка начинающееся с "NEW time at airport..."

    Также изменяем в файле MANIFEST.MF изменяем номер версии bundle c 1.0.3 на 1.0.4 (строка Bundle-Version).
  5. выполняем обновление TimeService bundle (команда update)

    osgi> update 51
    Event 'UNREGISTERING'(4) for service [{lv.nixx.osgi.sample.serviceinterface.TimeService}={description=TimeService, provide service to get time in airport by code, service.id=42}] is  fired
    'TimeService' bundle stoped
    Event 'REGISTERED' (1) for service [{lv.nixx.osgi.sample.serviceinterface.TimeService}={description=TimeService, provide service to get time in airport by code, service.id=43}] is  fired
    'TimeService' bundle started

    По логам видим, что листенер получил события UNREGISTERING и REGISTERED, по второму событию мы обновили ссылку на TimeService в сервисе WSTimeService
  6. выполняем запрос от клиента
    27/06/12 15:31:25,270 [main] TimeServer response [NEW time at airport, code:RIX(Riga Airport) [GMT+03:00 2012.06.27 15.31.025]]

    По логу клиента видим, что сервер ответил новой строкой, т.е мы изменили поведения сервера и изменения стали доступны клиенту без перезапуска сервера.
     
  7. проверяем состояние bundle (команда ss)
    osgi> ss

    Framework is launched.

    id    State       Bundle
    0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
    2    ACTIVE      osgi.cmpn_4.2.0.200908310645
    48    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
    49    ACTIVE      WSTimeServer_1.0.0.qualifier
    51    ACTIVE      TimeService_1.0.4.qualifier
    52    ACTIVE      TimeServiceInterface_1.0.0.qualifier
    53    ACTIVE      AirportInfoService_1.0.4.qualifier
    Видим, что изменился номер версии TimeService на 1.0.4.
Если после запуска контейнера запустить тест, который бесконечно отправляет запросы на сервер, то по его логам будет видно, обновление компонента TimeService не повлияет на его работу, т.е после изменнения возвращаемой строки клиент просто начнет получать новую строку. Ниже показан пример лога.
27/06/12 15:58:25,375 [main] TimeServer response [time at airport, code:LGW(London Gatwick Airport) [GMT+00:00 2012.06.27 12.58.025]]
27/06/12 15:58:25,884 [main] TimeServer response [time at airport, code:SVO(Moscow Sheremetyevo Airport) [GMT+03:00 2012.06.27 15.58.025]]
27/06/12 15:58:26,393 [main] TimeServer response [time at airport, code:CDG(Paris-Charles de Gaulle Airport) [GMT+02:00 2012.06.27 14.58.026]]
27/06/12 15:58:26,902 [main] TimeServer response [NEW time at airport, code:SVO(Moscow Sheremetyevo Airport) [GMT+03:00 2012.06.27 15.58.026]]
27/06/12 15:58:27,418 [main] TimeServer response [NEW time at airport, code:LGW(London Gatwick Airport) [GMT+00:00 2012.06.27 12.58.027]]
27/06/12 15:58:27,926 [main] TimeServer response [NEW time at airport, code:LGW(London Gatwick Airport) [GMT+00:00 2012.06.27 12.58.027]]

Обновление AirportInfoService bundle

Тестовый сценарий

  1. запускаем контейнер с установленными bundle
  2. проверяем состояние bundle (команда ss)
  3. выполняем запрос от клиента
  4. изменим строку возвращаемую AirportInfoProvider
  5. выполняем обновление AirportInfoService bundle (команда update)
  6. выполняем запрос от клиента - значение не меняется
  7. выполняем обновление AirportInfoService bundle (команда refresh)
  8. выполняем запрос от клиента - значение меняется на новое
  9. проверяем состояние bundle (команда ss)
Ожидаемый результат: третий запрос от клиента получил измененную строку

Выполнение тестового сценария

  1. запускаем контейнер с установленными bundle
  2. проверяем состояние bundle (команда ss)

    Framework is launched.

    id    State       Bundle
    0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
    2    ACTIVE      osgi.cmpn_4.2.0.200908310645
    48    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
    49    ACTIVE      WSTimeServer_1.0.0.qualifier
    51    ACTIVE      TimeService_1.0.4.qualifier
    52    ACTIVE      TimeServiceInterface_1.0.0.qualifier
    53    ACTIVE      AirportInfoService_1.0.4.qualifier
    Обращаем внимание на номер версии AirportInfoService - 1.0.4
  3. выполняем запрос от клиента

    27/06/12 15:44:30,172 [main] TimeServer response [NEW time at airport, code:LGW(London Gatwick Airport) [GMT+00:00 2012.06.27 12.44.030]]
  4. изменим строку возвращаемую AirportInfoProvider
    В классе AirportInfoProvider меняем строку:
                    "RIX", "Riga Airport"));
    на
                    "RIX", "NEW Riga Airport"));В файле MANIFEST.MF меняем номер версии на 1.0.5
  5. выполняем обновление AirportInfoService bundle (команда update)
    osgi> update 53
    В логе ничего нет, bundle не содержит активатора
  6. выполняем запрос от клиента - значение не меняется
    27/06/12 15:48:59,767 [main] TimeServer response [NEW time at airport, code:RIX(Riga Airport) [GMT+03:00 2012.06.27 15.48.059]]
  7. выполняем обновление AirportInfoService bundle (команда refresh)
    osgi> refresh 53

    osgi> Event 'UNREGISTERING'(4) for service [{lv.nixx.osgi.sample.serviceinterface.TimeService}={description=TimeService, provide service to get time in airport by code, service.id=42}] is  fired
    'TimeService' bundle stoped
    Event 'REGISTERED' (1) for service [{lv.nixx.osgi.sample.serviceinterface.TimeService}={description=TimeService, provide service to get time in airport by code, service.id=43}] is  fired
    'TimeService' bundle started

    По логу видим, что обновился зависимый bundle - TimeService
  8. выполняем запрос от клиента - значение меняется на новое
    27/06/12 15:51:29,353 [main] TimeServer response [NEW time at airport, code:RIX(NEW Riga Airport) [GMT+03:00 2012.06.27 15.51.029]]Значение, возвращаемое клиенту - поменялось

  9. проверяем состояние bundle (команда ss)
    Framework is launched.

    id    State       Bundle
    0    ACTIVE      org.eclipse.osgi_3.6.2.R36x_v20110210
    2    ACTIVE      osgi.cmpn_4.2.0.200908310645
    48    ACTIVE      cxf-dosgi-ri-singlebundle-distribution_1.3.0
    49    ACTIVE      WSTimeServer_1.0.0.qualifier
    51    ACTIVE      TimeService_1.0.4.qualifier
    52    ACTIVE      TimeServiceInterface_1.0.0.qualifier
    53    ACTIVE      AirportInfoService_1.0.5.qualifier
    Номер версии AirportInfoService - изменился на 1.0.5













Комментариев нет:

Отправить комментарий