В недавно вышедшей спецификации OSGi 4.2 декларировано такое новшество, как удаленные сервисы известные ранее, как Distributed OSGi или RFC 119. Давайте рассмотрим эту технологию, применительно к Eclipse Equinox.
RFC 119 реализуется в рамках Equinox с помощью контейнера ecf.r_osgi.peer, реализующего API удаленных сервисов. Для простоты будем рассматривать две Java-машины, на каждой из которых будет запускаться OSGi, причем ту машину, на которой выставляется сервис, назовем хостовой (host), а ту, на которой сервис будет использоваться - клиентской (client).
Коротко рассмотрим алгоритмы выставления удаленного сервиса и его использования (пока ограничимся использованием обычных, а не декларативных сервисов, поэтому все действия производятся в активаторе бандла).
Алгоритм выставления сервиса:
1. Получить экземпляр IContainerManager - сервиса.
2. С помощью полученного ContainerManager создать новый контейнер типа ecf.r_osgi.peer
3. Зарегистрировать свой сервис, установив ему свойство osgi.remote.interfaces, равным * (данные константы определены в интерфейсе IDistributionConstants, как REMOTE_INTERFACES и REMOTE_INTERFACES_WILDCARD, соответственно). Именно это свойство говорит OSGi-платформе о том, что нужно разрешить удаленное использование сервиса.
Алгоритм подключения к сервису:
1. Получить экземпляр IContainerManager - сервиса.
2. С помощью полученного ContainerManager создать новый контейнер типа ecf.r_osgi.peer
3. Создать новый ServiceTracker, задав фильтр, под который попадает удаленный сервис. Согласно спецификации RFC 119 фильтр должен включать условие (" + REMOTE + "=*), где REMOTE - соответствующая константа, определенная в IDistributionConstants.
4. Открыть созданный ServiceTracker и работать с ним так же, как и при использовании обычного сервиса.
Давайте теперь перейдем к рассмотрению примера.
Для примера создадим новый бандл name.samolisov.ecf.hello (в Eclipse я создаю бандлы, как Plug-In проекты, при этом автоматически генерируется манифест, можно быстро экспортировать бандл в jar и легко запустить в OSGi-фреймворке). В данном бандле определим интерфейс нашего сервиса, назовем его IHello:
Напишем простую реализацию, в которой будем выводить на консоль приветствие и его автора:
package name.samolisov.ecf.hello;
public class SimpleHello implements IHello {
public void hello(String from) {
System.out.println("Hello from " + from);
}
}
public class SimpleHello implements IHello {
public void hello(String from) {
System.out.println("Hello from " + from);
}
}
Активатор для данного бандла не нужен. Главное - не забыть указать в манифесте, что пакет name.samolisov.ecf.hello является экспортируемым:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello
Bundle-SymbolicName: name.samolisov.ecf.hello
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: name.samolisov.ecf.hello
Bundle-ManifestVersion: 2
Bundle-Name: Hello
Bundle-SymbolicName: name.samolisov.ecf.hello
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: name.samolisov.ecf.hello
Данный бандл мы будем использовать для того, чтобы сделать доступным IHello на нескольких VM и избавиться от копипасты.
Теперь создадим бандл, который будет хостом, т.е. будет выставлять распределяемый сервис. Назовем его name.samolisov.ecf.rosgi.host. При создании укажем, что нам нужен активатор. Код активатора следующий:
package name.samolisov.ecf.rosgi.host;
import java.util.Properties;
import name.samolisov.ecf.hello.IHello;
import name.samolisov.ecf.hello.SimpleHello;
import org.eclipse.ecf.core.IContainerManager;
import org.eclipse.ecf.osgi.services.distribution.IDistributionConstants;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator, IDistributionConstants
{
private BundleContext _context;
private ServiceTracker _containerManagerServiceTracker;
private ServiceRegistration _helloRegistration;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception
{
_context = context;
IContainerManager containerManager = getContainerManagerService();
containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
Properties props = new Properties();
props.put(REMOTE_INTERFACES, REMOTE_INTERFACES_WILDCARD);
_helloRegistration = context.registerService(IHello.class.getName(), new SimpleHello(), props);
System.out.println("Hello Service has been registered");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
if (_helloRegistration != null)
_helloRegistration.unregister();
if (_containerManagerServiceTracker != null)
_containerManagerServiceTracker.close();
}
private IContainerManager getContainerManagerService()
{
if (_containerManagerServiceTracker == null)
{
_containerManagerServiceTracker = new ServiceTracker(_context, IContainerManager.class.getName(), null);
_containerManagerServiceTracker.open();
}
return (IContainerManager) _containerManagerServiceTracker.getService();
}
}
import java.util.Properties;
import name.samolisov.ecf.hello.IHello;
import name.samolisov.ecf.hello.SimpleHello;
import org.eclipse.ecf.core.IContainerManager;
import org.eclipse.ecf.osgi.services.distribution.IDistributionConstants;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator, IDistributionConstants
{
private BundleContext _context;
private ServiceTracker _containerManagerServiceTracker;
private ServiceRegistration _helloRegistration;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception
{
_context = context;
IContainerManager containerManager = getContainerManagerService();
containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
Properties props = new Properties();
props.put(REMOTE_INTERFACES, REMOTE_INTERFACES_WILDCARD);
_helloRegistration = context.registerService(IHello.class.getName(), new SimpleHello(), props);
System.out.println("Hello Service has been registered");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
if (_helloRegistration != null)
_helloRegistration.unregister();
if (_containerManagerServiceTracker != null)
_containerManagerServiceTracker.close();
}
private IContainerManager getContainerManagerService()
{
if (_containerManagerServiceTracker == null)
{
_containerManagerServiceTracker = new ServiceTracker(_context, IContainerManager.class.getName(), null);
_containerManagerServiceTracker.open();
}
return (IContainerManager) _containerManagerServiceTracker.getService();
}
}
Как видно, этот код один в один реализует описанный выше алгоритм выставления распределяемого сервиса.
Манифест бандла следующий:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Host
Bundle-SymbolicName: name.samolisov.ecf.rosgi.host
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.host.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: name.samolisov.ecf.hello,
org.eclipse.ecf.core;version="3.0.0",
org.eclipse.ecf.core.identity;version="3.0.0",
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.2"
Require-Bundle: org.eclipse.equinox.common;bundle-version="3.5.0"
Bundle-ManifestVersion: 2
Bundle-Name: Host
Bundle-SymbolicName: name.samolisov.ecf.rosgi.host
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.host.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: name.samolisov.ecf.hello,
org.eclipse.ecf.core;version="3.0.0",
org.eclipse.ecf.core.identity;version="3.0.0",
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.2"
Require-Bundle: org.eclipse.equinox.common;bundle-version="3.5.0"
Стоит подробно остановиться на запуске бандла и самое главное - зависимостях. Чтобы подготовить конфигурацию запуска, необходимо в контекстном меню проекта выбрать Run As -> Run Configurations... В появившемся окне 2 раза щелкаем по OSGi Framework, создастся новая конфигурация запуска. Переименуем ее для ясности в My R-OSGi Host. После чего нужно нажать кнопку Deselect All, снять галочки с Include optional dependencies when computing required bundles и Add new workspace bundles to this launch configuration automatically, а так же выбрать следующие бандлы:
Workspace
name.samolisov.ecf.hello
name.samolisov.ecf.rosgi.host
Target Platform
ch.ethz.iks.r_osgi.remote
org.eclipse.core.jobs
org.eclipse.core.runtime.compability.registry
org.eclipse.ecf
org.eclipse.ecf.discovery
org.eclipse.ecf.identity
org.eclipse.ecf.osgi.services
org.eclipse.ecf.osgi.services.discovery
org.eclipse.ecf.osgi.services.distribution
org.eclipse.ecf.provider
org.eclipse.ecf.provider.jmdns
org.eclipse.ecf.provider.r_osgi
org.eclipse.ecf.remoteservice
org.eclipse.ecf.sharedobject
org.eclipse.ecf.ssl
org.eclipse.equinox.common
org.eclipse.equinox.concurrent
org.eclipse.equinox.registry
org.eclipse.osgi
org.eclipse.osgi.services
org.objectweb.asm
Причем у бандлов name.samolisov.ecf.rosgi.host, org.eclipse.osgi, org.eclipse.equinox.common необходимо выставить Auto-Start в true.
Пару слов скажу о бандле org.eclipse.ecf.provider.jmdns. Данный бандл реализует протоколы Zeroconf/Bonjour/Rendevous, используемые для публикации и обнаружение сервисов в локальной сети. В ECF представлена так же реализация протокола SLP: бандлы ch.ethz.iks.slp и org.eclipse.ecf.provider.jslp. Т.е. вместо бандла org.eclipse.ecf.provider.jmdns можно выбрать их, главное - чтобы одинаковые бандлы использовались и на хостовой и на клиентских VM. К сожалению, SLP так же ограничен одной локальной подсетью, но ECF предлагает Discovery API, поэтому можно написать свой механизм обнаружения сервисов.
Запускаем OSGi. При старте нам может быть выдано сообщение:
[log;-0700 2009.10.17 13:37:45:421;WARNING;org.eclipse.ecf.osgi.services.distribution;org.eclipse.core.runtime.Status[plugin=org.eclipse.ecf.osgi.services.distribution;code=4;message=org.eclipse.ecf.internal.osgi.services.distribution.DiscoveredServiceTrackerImpl:handleDiscoveredServiceAvailable:No RemoteServiceContainerAdapters found for description=ServiceEndpointDescriptionImpl[;providedinterfaces=[org.eclipse.ecf.examples.remoteservices.hello.IHello];location=null;serviceid=ServiceID[type=ServiceTypeID[typeName=_osgiservices._tcp.default._iana];location=osgiservices://10.0.1.18:9279;full=_osgiservices._tcp.default._iana@osgiservices://10.0.1.18:9279];osgiEndpointID=null;ecfEndpointID=r-osgi://TILAPIA:9279;ecfTargetID=null;ecfFilter=null;props={ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=org.eclipse.ecf.examples.remoteservices.hello.IHello, ecf.sp.cns=ecf.namespace.r_osgi, ecf.rsvc.id=[B@1950e0a, ecf.sp.cid=[B@ccd21c}];severity4;exception=null;children=[]]]
Его можно проигнорировать, оно говорит лишь о том, что после создания контейнера нашим бандлом были обнаружены какие- то системные сервисы.
Такое предупреждение:
osgi> WARNING: Port 9278 already in use. This instance of R-OSGi is running on port 9279
говорит о том, что в системе уже есть R-OSGi контейнер, скорее всего этот контейнер был создан при запуске Eclipse. Здесь написано, как запретить создание контейнеров при запуске Eclipse.
Выполним команду ss и убедимся, что наш бандл находится в состоянии ACTIVE:
23 ACTIVE name.samolisov.ecf.rosgi.host_1.0.0.qualifier
24 RESOLVED name.samolisov.ecf.hello_1.0.0.qualifier
24 RESOLVED name.samolisov.ecf.hello_1.0.0.qualifier
Команда services (objectClass=name.samolisov.ecf.hello.IHello) показывает, что сервис создан, зарегистрирован и используется бандлами для публикации:
{name.samolisov.ecf.hello.IHello}={osgi.remote.interfaces=*, service.id=45}
Registered by bundle: name.samolisov.ecf.rosgi.host_1.0.0.qualifier [23]
Bundles using service:
org.eclipse.ecf.osgi.services.distribution_1.0.0.v20091010-1900 [17]
{name.samolisov.ecf.hello.IHello}={service.id=46, service.remote.registration=true, ecf.rsvc.ranking=0, ecf.rsvc.id=46, ecf.robjectClass=[name.samolisov.ecf.hello.IHello], ecf.rsvc.cid=r-osgi://samolisov:9278}
Registered by bundle: org.eclipse.ecf.provider.r_osgi_3.0.0.v20091010-1900 [5]
Bundles using service:
ch.ethz.iks.r_osgi.remote_1.0.0.RC4_v20091010-1900 [11]
Registered by bundle: name.samolisov.ecf.rosgi.host_1.0.0.qualifier [23]
Bundles using service:
org.eclipse.ecf.osgi.services.distribution_1.0.0.v20091010-1900 [17]
{name.samolisov.ecf.hello.IHello}={service.id=46, service.remote.registration=true, ecf.rsvc.ranking=0, ecf.rsvc.id=46, ecf.robjectClass=[name.samolisov.ecf.hello.IHello], ecf.rsvc.cid=r-osgi://samolisov:9278}
Registered by bundle: org.eclipse.ecf.provider.r_osgi_3.0.0.v20091010-1900 [5]
Bundles using service:
ch.ethz.iks.r_osgi.remote_1.0.0.RC4_v20091010-1900 [11]
Теперь нужно создать бандл-клиент. Назовем его name.samolisov.ecf.rosgi.client, при создании так же укажем, что нам нужен активатор. Код активатора должен быть следующим:
package name.samolisov.ecf.rosgi.client;
import name.samolisov.ecf.hello.IHello;
import org.eclipse.ecf.core.IContainerManager;
import org.eclipse.ecf.osgi.services.distribution.IDistributionConstants;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class Activator implements BundleActivator, IDistributionConstants, ServiceTrackerCustomizer
{
public static final String CONSUMER_NAME = "name.samolisov.ecf.rosgi.client";
private BundleContext _context;
private ServiceTracker _containerManagerServiceTracker;
private ServiceTracker _helloServiceTracker;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception
{
_context = context;
IContainerManager containerManager = getContainerManagerService();
containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
_helloServiceTracker = new ServiceTracker(context, createRemoteFilter(), this);
_helloServiceTracker.open();
}
private Filter createRemoteFilter() throws InvalidSyntaxException
{
return _context.createFilter("(&("+org.osgi.framework.Constants.OBJECTCLASS + "=" + IHello.class.getName()
+ ")(" + REMOTE + "=*))");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
_helloServiceTracker.close();
if (_containerManagerServiceTracker != null)
_containerManagerServiceTracker.close();
}
public Object addingService(ServiceReference reference)
{
System.out.println("IHello service proxy being added");
IHello hello = (IHello) _context.getService(reference);
hello.hello(CONSUMER_NAME);
System.out.println("Called hello using proxy");
return hello;
}
public void modifiedService(ServiceReference reference, Object service)
{
}
public void removedService(ServiceReference reference, Object service)
{
}
private IContainerManager getContainerManagerService()
{
if (_containerManagerServiceTracker == null)
{
_containerManagerServiceTracker = new ServiceTracker(_context, IContainerManager.class.getName(), null);
_containerManagerServiceTracker.open();
}
return (IContainerManager) _containerManagerServiceTracker.getService();
}
}
import name.samolisov.ecf.hello.IHello;
import org.eclipse.ecf.core.IContainerManager;
import org.eclipse.ecf.osgi.services.distribution.IDistributionConstants;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class Activator implements BundleActivator, IDistributionConstants, ServiceTrackerCustomizer
{
public static final String CONSUMER_NAME = "name.samolisov.ecf.rosgi.client";
private BundleContext _context;
private ServiceTracker _containerManagerServiceTracker;
private ServiceTracker _helloServiceTracker;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception
{
_context = context;
IContainerManager containerManager = getContainerManagerService();
containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
_helloServiceTracker = new ServiceTracker(context, createRemoteFilter(), this);
_helloServiceTracker.open();
}
private Filter createRemoteFilter() throws InvalidSyntaxException
{
return _context.createFilter("(&("+org.osgi.framework.Constants.OBJECTCLASS + "=" + IHello.class.getName()
+ ")(" + REMOTE + "=*))");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception
{
_helloServiceTracker.close();
if (_containerManagerServiceTracker != null)
_containerManagerServiceTracker.close();
}
public Object addingService(ServiceReference reference)
{
System.out.println("IHello service proxy being added");
IHello hello = (IHello) _context.getService(reference);
hello.hello(CONSUMER_NAME);
System.out.println("Called hello using proxy");
return hello;
}
public void modifiedService(ServiceReference reference, Object service)
{
}
public void removedService(ServiceReference reference, Object service)
{
}
private IContainerManager getContainerManagerService()
{
if (_containerManagerServiceTracker == null)
{
_containerManagerServiceTracker = new ServiceTracker(_context, IContainerManager.class.getName(), null);
_containerManagerServiceTracker.open();
}
return (IContainerManager) _containerManagerServiceTracker.getService();
}
}
В методе start мы производим создание контейнера и ServiceTracker'а. Причем в качестве ServiceTrackerCustomizer'а мы указываем сам активатор. Метод createRemoteFilter создает фильтр, отбирающий сервисы, реализующие интерфейс IHello и зарегистрированные, как разделяемые (то, что при регистрации указывался параметр REMOTE_INTERFACES, а в фильтре - REMOTE, это - не ошибка).
Метод addingService кастомайзера треккера вызывается при регистрации сервиса, прошедшего фильтр. Именно в нем разумно помещать код, вызывающий данный сервис. Мы просто создаем прокси для сервиса (с помощью _context.getService(reference)) и вызываем метод hello.
Манифест бандла должен быть таким:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Client
Bundle-SymbolicName: name.samolisov.ecf.rosgi.client
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.client.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: name.samolisov.ecf.hello,
org.eclipse.ecf.core;version="3.0.0",
org.eclipse.ecf.core.identity;version="3.0.0",
org.eclipse.ecf.core.util,
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.eclipse.ecf.remoteservice,
org.eclipse.equinox.concurrent.future;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.2"
Require-Bundle: org.eclipse.equinox.common;bundle-version="3.5.0"
Bundle-ManifestVersion: 2
Bundle-Name: Client
Bundle-SymbolicName: name.samolisov.ecf.rosgi.client
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.client.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: name.samolisov.ecf.hello,
org.eclipse.ecf.core;version="3.0.0",
org.eclipse.ecf.core.identity;version="3.0.0",
org.eclipse.ecf.core.util,
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.eclipse.ecf.remoteservice,
org.eclipse.equinox.concurrent.future;version="1.0.0",
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.2"
Require-Bundle: org.eclipse.equinox.common;bundle-version="3.5.0"
Теперь создадим конфигурацию запуска бандла. Войдем в Run Configurations... и сделаем Duplicate нашей предыдущей конфигурации. Назовем новую конфигурацию, например, My R-OSGi Client. Снимем галочку с name.samolisov.ecf.rosgi.host и отметим ею name.samolisov.ecf.rosgi.client. Установим у этого бандла Auto-Start в true.
Запустим OSGi Framework. Через некоторое время после старта в консоли увидим следующее:
IHello service proxy being added
Called hello using proxy
Called hello using proxy
В то же время в консоль хостовой VM будет выведено:
Hello from name.samolisov.ecf.rosgi.client
Видно, что распределенные сервисы работают: сервис, зарегистрированный на одной VM вызывается из другой. Причем работает он именно на той VM, на которой был зарегистрирован.
Давайте выполним в клиентской консоли команду ss, мы должны увидить, что появился бандл с прокси:
23 ACTIVE R-OSGi Proxy Bundle generated for Endpoint r-osgi://samolisov:9278#46_0.0.0
Если выполнить команду services (objectClass=name.samolisov.ecf.hello.IHello), то будет видно, как используется прокси на сервис:
{name.samolisov.ecf.hello.IHello}={ecf.rsvc.cid=r-osgi://samolisov:9278, ecf.rsvc.ranking=0, ecf.rsvc.id=46, service.uri=r-osgi://samolisov:9278#46, ecf.robjectClass=[name.samolisov.ecf.hello.IHello], service.id=45}
Registered by bundle: R-OSGi Proxy Bundle generated for Endpoint r-osgi://samolisov:9278#46_0.0.0 [23]
Bundles using service:
ch.ethz.iks.r_osgi.remote_1.0.0.RC4_v20091010-1900 [6]
{name.samolisov.ecf.hello.IHello}={service.id=46, osgi.remote=org.eclipse.ecf.internal.provider.r_osgi.RemoteServiceImpl@425743, osgi.remote.endpoint.id=r-osgi://samolisov:9278#46}
Registered by bundle: org.eclipse.ecf.osgi.services.distribution_1.0.0.v20091010-1900 [8]
Bundles using service:
name.samolisov.ecf.rosgi.client_1.0.0.qualifier [9]
Registered by bundle: R-OSGi Proxy Bundle generated for Endpoint r-osgi://samolisov:9278#46_0.0.0 [23]
Bundles using service:
ch.ethz.iks.r_osgi.remote_1.0.0.RC4_v20091010-1900 [6]
{name.samolisov.ecf.hello.IHello}={service.id=46, osgi.remote=org.eclipse.ecf.internal.provider.r_osgi.RemoteServiceImpl@425743, osgi.remote.endpoint.id=r-osgi://samolisov:9278#46}
Registered by bundle: org.eclipse.ecf.osgi.services.distribution_1.0.0.v20091010-1900 [8]
Bundles using service:
name.samolisov.ecf.rosgi.client_1.0.0.qualifier [9]
R-OSGi позволяет вызывать сервисы не только синхронно (как в показанном выше примере), но и асинхронно. Асинхронное взаимодействие в данном случае обозначает, что в момент вызова сервису будет отдана команда на исполнение, после чего вызвавший его код (т.е. клиент) будет продолжать работу. Когда сервис закончит исполнение команды, он возвращает результат, который клиент может использовать.
Давайте для наглядности введем временные отметки. Сначала изменим класс Simple Hello таким образом:
package name.samolisov.ecf.hello;
import java.util.Date;
public class SimpleHello implements IHello {
public void hello(String from) {
System.out.println((new Date()) + " Start Hello from " + from);
pause();
System.out.println((new Date()) + " End Hello from " + from);
}
private void pause()
{
for (long i = 0; i < 1000000; i++)
for (int j = 0; j < 100; j++)
{
Math.random();
}
}
}
import java.util.Date;
public class SimpleHello implements IHello {
public void hello(String from) {
System.out.println((new Date()) + " Start Hello from " + from);
pause();
System.out.println((new Date()) + " End Hello from " + from);
}
private void pause()
{
for (long i = 0; i < 1000000; i++)
for (int j = 0; j < 100; j++)
{
Math.random();
}
}
}
С помощью метода pause мы имитируем долгие вычисления, которые совершает сервис. Теперь добавим в метод addingService следующий код:
IRemoteService remoteService = (IRemoteService) reference.getProperty(REMOTE);
try
{
System.out.println((new Date()) + " Call hello asynchronously");
remoteService.fireAsync(new IRemoteCall() {
public String getMethod() {
return "hello";
}
public Object[] getParameters() {
return new Object[] { CONSUMER_NAME + " async" };
}
public long getTimeout() {
return 30000;
}});
System.out.println((new Date()) + " Called hello asynchronously");
}
catch (ECFException e) {
e.printStackTrace();
}
try
{
System.out.println((new Date()) + " Call hello asynchronously");
remoteService.fireAsync(new IRemoteCall() {
public String getMethod() {
return "hello";
}
public Object[] getParameters() {
return new Object[] { CONSUMER_NAME + " async" };
}
public long getTimeout() {
return 30000;
}});
System.out.println((new Date()) + " Called hello asynchronously");
}
catch (ECFException e) {
e.printStackTrace();
}
Интерфейс IRemoteService определен в бандле org.eclipse.ecf.remoteservice и является частью Remote Services API. Свойство REMOTE как раз хранит ссылку на удаленный сервис. Основным методом интерфейса IRemoteService является fireAsync который принимает описание вызова метода. Описание вызова представлено интерфейсом IRemoteCall, который позволяет задать имя вызываемого метода, значения параметров и таймаут - максимальную задержку, при привышении которой будет сгенерировано исключение.
Запустим хост и клиент. В клиентскую OSGi консоль будет выведено:
Sun Oct 18 13:31:20 YEKST 2009 Call hello asynchronously
Sun Oct 18 13:31:20 YEKST 2009 Called hello asynchronously
Sun Oct 18 13:31:20 YEKST 2009 Called hello asynchronously
В то же время в хостовой консоли мы увидим:
Sun Oct 18 13:31:20 YEKST 2009 Start Hello from name.samolisov.ecf.rosgi.client async
Sun Oct 18 13:32:03 YEKST 2009 End Hello from name.samolisov.ecf.rosgi.client async
Sun Oct 18 13:32:03 YEKST 2009 End Hello from name.samolisov.ecf.rosgi.client async
Т.е. сначала была послана команда, сервис начал ее выполнять, клиент при этом продолжил свою работу и лишь после этого сервис завершил вычисления.
Существует так же другой способ обеспечить асинхронный вызов. Этот способ - использование механизма IFuture определенного в бандле org.eclipse.equinox.concurrent. Это - общий механизм Equinox, а не только ECF. Суть использования данного механизма в следующем: когда мы хотим послать асинхронный запрос сервису - создаем экземпляр класса, реализующего интерфейс IFuture с помощью вызова метода futureExec. Далее можно продолжать работу. В том месте, где мы хотим использовать результат от сервиса, получаем его при помощи IFuture#get(). При этом, если результат уже был вычислен, то мы его получим, если же нет - наш поток будет заблокирован до тех пор, пока сервис не вычислит результат.
Для примера добавим в метод addingService активатора бандла-клиента следующий код:
IRemoteService remoteService = (IRemoteService) reference.getProperty(REMOTE);
System.out.println((new Date()) + " Call hello using future");
IFuture future = RemoteServiceHelper.futureExec(remoteService, "hello",
new Object[] { CONSUMER_NAME + " future" });
try {
// This method blocks until a return
future.get();
System.out.println((new Date()) + " Called hello using future");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println((new Date()) + " Call hello using future");
IFuture future = RemoteServiceHelper.futureExec(remoteService, "hello",
new Object[] { CONSUMER_NAME + " future" });
try {
// This method blocks until a return
future.get();
System.out.println((new Date()) + " Called hello using future");
} catch (Exception e) {
e.printStackTrace();
}
Запустим измененый бандл (при запущенном хосте, естественно). Увидим в OSGi консоли следующее:
Sun Oct 18 13:46:49 YEKST 2009 Call hello using future
Sun Oct 18 13:47:13 YEKST 2009 Called hello using future
Sun Oct 18 13:47:13 YEKST 2009 Called hello using future
В то же время в консоли сервиса:
Sun Oct 18 13:46:49 YEKST 2009 Start Hello from name.samolisov.ecf.rosgi.client future
Sun Oct 18 13:47:13 YEKST 2009 End Hello from name.samolisov.ecf.rosgi.client future
Sun Oct 18 13:47:13 YEKST 2009 End Hello from name.samolisov.ecf.rosgi.client future
Т.е. видно, что клиент был заблокирован в момент вызова IFuture#get и ожидал завершения работы сервиса.
Напоследок хочется еще рассказать про механизм Discovery и Distribution листенеров. В хостовом OSGi-фреймворке можно зарегистрировать два сервиса, реализующих интерфейсы IHostDistributionListener и IHostDiscoveryListener соответственно. Сервис, реализующий IHostDistributionListener, будет вызываться при регистрации и разрегистрации выставляемого хостом удаленного сервиса. Сервис, реализующий IHostDiscoveryListener, будет вызываться при публикации, т.е. после того, как сервис станет доступен для обнаружения удаленным клиентом и, соответственно, при депубликации.
На клиентской стороне в свою очередь можно зарегистрировать сервисы, реализующие интерфейсы IProxyDiscoveryListener (будет вызываться при обнаружении удаленного сервиса) и IProxyDistributionListener (будет вызываться, когда будет найден удаленный ECF-контейнер и, соответственно, создан прокси для удаленного бандла, выставившего сервис).
Давайте напишем простейшие реализации всех этих сервисов. Сделаем для хостовых и клиенских листенеров отдельные бандлы: name.samolisov.ecf.rosgi.host.listener и name.samolisov.ecf.rosgi.client.listener.
В бандле name.samolisov.ecf.rosgi.host.listener определим класс MyHostDiscoveryListener:
package name.samolisov.ecf.rosgi.host.listener;
import org.eclipse.ecf.discovery.IServiceInfo;
import org.eclipse.ecf.osgi.services.discovery.IHostDiscoveryListener;
import org.osgi.framework.ServiceReference;
public class MyHostDiscoveryListener implements IHostDiscoveryListener {
@Override
public void publish(ServiceReference publicationServiceReference,
IServiceInfo serviceInfo) {
System.out.println("service publish\n\tpublicationServiceReference = " + publicationServiceReference +
"\n\tserviceInfo = " + serviceInfo);
}
@Override
public void unpublish(ServiceReference publicationServiceReference,
IServiceInfo serviceInfo) {
System.out.println("service unpublish\n\tpublicationServiceReference = " + publicationServiceReference +
"\n\tserviceInfo = " + serviceInfo);
}
}
import org.eclipse.ecf.discovery.IServiceInfo;
import org.eclipse.ecf.osgi.services.discovery.IHostDiscoveryListener;
import org.osgi.framework.ServiceReference;
public class MyHostDiscoveryListener implements IHostDiscoveryListener {
@Override
public void publish(ServiceReference publicationServiceReference,
IServiceInfo serviceInfo) {
System.out.println("service publish\n\tpublicationServiceReference = " + publicationServiceReference +
"\n\tserviceInfo = " + serviceInfo);
}
@Override
public void unpublish(ServiceReference publicationServiceReference,
IServiceInfo serviceInfo) {
System.out.println("service unpublish\n\tpublicationServiceReference = " + publicationServiceReference +
"\n\tserviceInfo = " + serviceInfo);
}
}
Так же определим класс MyHostDistributionListener, имеющий следующее содержание:
package name.samolisov.ecf.rosgi.host.listener;
import org.eclipse.ecf.osgi.services.distribution.IHostDistributionListener;
import org.eclipse.ecf.remoteservice.IRemoteServiceContainer;
import org.eclipse.ecf.remoteservice.IRemoteServiceRegistration;
import org.osgi.framework.ServiceReference;
public class MyHostDistributionListener implements IHostDistributionListener {
@Override
public void registered(ServiceReference serviceReference,
IRemoteServiceContainer remoteServiceContainer,
IRemoteServiceRegistration remoteRegistration) {
System.out.println("hostRegistered\n\tserviceReference = " + serviceReference +
"\n\tremoteServiceContainer = " + remoteServiceContainer +
"\n\tremoteRegistration = " + remoteRegistration);
}
@Override
public void unregistered(ServiceReference serviceReference,
IRemoteServiceRegistration remoteRegistration) {
System.out.println("hostUnregistered\n\tserviceReference = " + serviceReference +
"\n\tremoteRegistration = " + remoteRegistration);
}
}
import org.eclipse.ecf.osgi.services.distribution.IHostDistributionListener;
import org.eclipse.ecf.remoteservice.IRemoteServiceContainer;
import org.eclipse.ecf.remoteservice.IRemoteServiceRegistration;
import org.osgi.framework.ServiceReference;
public class MyHostDistributionListener implements IHostDistributionListener {
@Override
public void registered(ServiceReference serviceReference,
IRemoteServiceContainer remoteServiceContainer,
IRemoteServiceRegistration remoteRegistration) {
System.out.println("hostRegistered\n\tserviceReference = " + serviceReference +
"\n\tremoteServiceContainer = " + remoteServiceContainer +
"\n\tremoteRegistration = " + remoteRegistration);
}
@Override
public void unregistered(ServiceReference serviceReference,
IRemoteServiceRegistration remoteRegistration) {
System.out.println("hostUnregistered\n\tserviceReference = " + serviceReference +
"\n\tremoteRegistration = " + remoteRegistration);
}
}
В активаторе бандла эти сервисы регистрируются следующим образом:
public void start(BundleContext context) throws Exception {
_hostDistributionListenerRegistration = context.registerService(IHostDistributionListener.class.getName(),
new MyHostDistributionListener(), null);
_hostDiscoveryListenerRegistration = context.registerService(IHostDiscoveryListener.class.getName(),
new MyHostDiscoveryListener(), null);
}
_hostDistributionListenerRegistration = context.registerService(IHostDistributionListener.class.getName(),
new MyHostDistributionListener(), null);
_hostDiscoveryListenerRegistration = context.registerService(IHostDiscoveryListener.class.getName(),
new MyHostDiscoveryListener(), null);
}
Манифест бандла:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HostListener
Bundle-SymbolicName: name.samolisov.ecf.rosgi.host.listener
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.host.listener.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.eclipse.ecf.discovery;version="3.0.0",
org.eclipse.ecf.osgi.services.discovery;version="1.0.0",
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.eclipse.ecf.remoteservice,
org.osgi.framework;version="1.3.0"
Bundle-ManifestVersion: 2
Bundle-Name: HostListener
Bundle-SymbolicName: name.samolisov.ecf.rosgi.host.listener
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: name.samolisov.ecf.rosgi.host.listener.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.eclipse.ecf.discovery;version="3.0.0",
org.eclipse.ecf.osgi.services.discovery;version="1.0.0",
org.eclipse.ecf.osgi.services.distribution;version="1.0.0",
org.eclipse.ecf.remoteservice,
org.osgi.framework;version="1.3.0"
Далее нужно настроить конфигурацию запуска. Добавим данный бандл в конфигурацию запуска My R-OSGi Host. Понятно, что для срабатывания листенеров нужно сделать так, чтобы они зарегистрировались до нашего распределяемого сервиса. Т.е. бандл name.samolisov.ecf.rosgi.host.listener должен стартовать до бандла name.samolisov.ecf.rosgi.host. Для этого данному бандлу нужно выставить Auto-Start в true, а Start Level установить в 1. После этого нужно нажать кнопку Add required bundles, чтобы разрешить все зависимости.
После запуска OSGi мы увидим в консоли следующее:
osgi> service publish
publicationServiceReference = {org.osgi.service.discovery.ServicePublication}={osgi.remote.discovery.publication.service.properties={}, ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=[name.samolisov.ecf.hello.IHello], ecf.rsvc.id=[52,56], ecf.sp.cid=r-osgi://samolisov:9278, service.id=49}
serviceInfo = ServiceInfo[uri=osgiservices://127.0.0.1:9278;id=ServiceID[type=ServiceTypeID[typeName=_osgiservices._tcp.default._iana];location=osgiservices://127.0.0.1:9278;full=_osgiservices._tcp.default._iana@osgiservices://127.0.0.1:9278];priority=0;weight=0;props=ServiceProperties[{ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=name.samolisov.ecf.hello.IHello, ecf.sp.cns=ecf.namespace.r_osgi, ecf.rsvc.id=org.eclipse.ecf.discovery.ServiceProperties$ByteArrayWrapper@2d189c, ecf.sp.cid=org.eclipse.ecf.discovery.ServiceProperties$ByteArrayWrapper@aae86e}]]
[log;+0600 2009.10.18 14:50:19:951;WARNING;org.eclipse.ecf.osgi.services.distribution;org.eclipse.core.runtime.Status[plugin=org.eclipse.ecf.osgi.services.distribution;code=2;message=org.eclipse.ecf.internal.osgi.services.distribution.DiscoveredServiceTrackerImpl:handleDiscoveredServiceAvailable:No RemoteServiceContainers found for description=ServiceEndpointDescriptionImpl[;providedinterfaces=[name.samolisov.ecf.hello.IHello];location=null;remoteServiceId=48;discoveryServiceID=ServiceID[type=ServiceTypeID[typeName=_osgiservices._tcp.default._iana];location=osgiservices://127.0.0.1:9278;full=_osgiservices._tcp.default._iana@osgiservices://127.0.0.1:9278];endpointID=null;endpointAsID=r-osgi://samolisov:9278;connectTargetID=null;remoteServicesFilter=null;props={ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=name.samolisov.ecf.hello.IHello, ecf.sp.cns=ecf.namespace.r_osgi, ecf.rsvc.id=[B@165b7e, ecf.sp.cid=[B@1d0d124}];severity2;exception=null;children=[]]]
hostRegistered
serviceReference = {name.samolisov.ecf.hello.IHello}={osgi.remote.interfaces=*, service.id=47}
remoteServiceContainer = RemoteServiceContainer [containerID=r-osgi://samolisov:9278, container=org.eclipse.ecf.internal.provider.r_osgi.R_OSGiRemoteServiceContainer@2ab653, containerAdapter=org.eclipse.ecf.internal.provider.r_osgi.R_OSGiRemoteServiceContainer@2ab653]
remoteRegistration = org.eclipse.ecf.internal.provider.r_osgi.RemoteServiceRegistrationImpl@1d439fe
Hello Service has been registered
publicationServiceReference = {org.osgi.service.discovery.ServicePublication}={osgi.remote.discovery.publication.service.properties={}, ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=[name.samolisov.ecf.hello.IHello], ecf.rsvc.id=[52,56], ecf.sp.cid=r-osgi://samolisov:9278, service.id=49}
serviceInfo = ServiceInfo[uri=osgiservices://127.0.0.1:9278;id=ServiceID[type=ServiceTypeID[typeName=_osgiservices._tcp.default._iana];location=osgiservices://127.0.0.1:9278;full=_osgiservices._tcp.default._iana@osgiservices://127.0.0.1:9278];priority=0;weight=0;props=ServiceProperties[{ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=name.samolisov.ecf.hello.IHello, ecf.sp.cns=ecf.namespace.r_osgi, ecf.rsvc.id=org.eclipse.ecf.discovery.ServiceProperties$ByteArrayWrapper@2d189c, ecf.sp.cid=org.eclipse.ecf.discovery.ServiceProperties$ByteArrayWrapper@aae86e}]]
[log;+0600 2009.10.18 14:50:19:951;WARNING;org.eclipse.ecf.osgi.services.distribution;org.eclipse.core.runtime.Status[plugin=org.eclipse.ecf.osgi.services.distribution;code=2;message=org.eclipse.ecf.internal.osgi.services.distribution.DiscoveredServiceTrackerImpl:handleDiscoveredServiceAvailable:No RemoteServiceContainers found for description=ServiceEndpointDescriptionImpl[;providedinterfaces=[name.samolisov.ecf.hello.IHello];location=null;remoteServiceId=48;discoveryServiceID=ServiceID[type=ServiceTypeID[typeName=_osgiservices._tcp.default._iana];location=osgiservices://127.0.0.1:9278;full=_osgiservices._tcp.default._iana@osgiservices://127.0.0.1:9278];endpointID=null;endpointAsID=r-osgi://samolisov:9278;connectTargetID=null;remoteServicesFilter=null;props={ecf.rsvc.ns=ecf.namespace.r_osgi.remoteservice, osgi.remote.service.interfaces=name.samolisov.ecf.hello.IHello, ecf.sp.cns=ecf.namespace.r_osgi, ecf.rsvc.id=[B@165b7e, ecf.sp.cid=[B@1d0d124}];severity2;exception=null;children=[]]]
hostRegistered
serviceReference = {name.samolisov.ecf.hello.IHello}={osgi.remote.interfaces=*, service.id=47}
remoteServiceContainer = RemoteServiceContainer [containerID=r-osgi://samolisov:9278, container=org.eclipse.ecf.internal.provider.r_osgi.R_OSGiRemoteServiceContainer@2ab653, containerAdapter=org.eclipse.ecf.internal.provider.r_osgi.R_OSGiRemoteServiceContainer@2ab653]
remoteRegistration = org.eclipse.ecf.internal.provider.r_osgi.RemoteServiceRegistrationImpl@1d439fe
Hello Service has been registered
Можно поизучать данный вывод, рассмотреть параметры регистрации и публикации сервиса.
Создание бандла, реализующего IProxyDistributionListener и IProxyDiscoveryListener, оставляю вам в качестве упражнения.
Как всегда готов ответить на любые вопросы и рад конструктивным замечаниям.
Скачать примеры к статье (исходники, class-файлы, launch'еры. Rar, 33кб)
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter
1 комментарий:
Если в проекте нет необходимости использовать асинхронные сервисы, то возможно лучше воспользоваться непосредственным r-osgi http://r-osgi.sourceforge.net/userguide.html. Как минимум, получается меньше кода.
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!