пятница, 23 октября 2009 г.

ECF: Используем Remote Services API


При знакомстве с Eclipse Communication Framework'ом мы отметили, что некоторые его контейнеры поддерживают несколько разнородных API. В частности, R-OSGi и Generiс контейнеры, а так же появившийся в недавно вышедшем ECF 3.1 REST контейнер, поддерживают API вызова удаленных сервисов, т.н. Remote Services API.

Давайте поговорим о том, что можно делать с помощью данного API, а затем о том, как его использовать.

ECF Remote Services API это еще один (наряду с R-OSGi) способ обеспечения работы бандлов в распределенной среде. Точно так же одни бандлы могут выставлять сервисы, доступные другим бандлам на другой JVM (и, естественно, даже на другой машине). Отличие от R-OSGi в том, что данная система, во-первых, не стандартизирована (т.е. это не OSGi подсистема, а часть непосредственно ECF), а, во-вторых, - более гибка, т.к. можно явно указывать, где находятся бандлы, выставляющие сервисы, (т.е. отпадает необходимость в процедуре поиска сервисов) и использовать для обеспечения взаимодействия различные протоколы и реализации (ECF Generic, Active MQ, Weblogic, JavaGroups и даже XMPP).


Основной механизм, через который осуществляется использование данного API (как, впрочем, и других API фреймворка) - механизм адаптеров. После создания экземпляра любого контейнера, поддерживающего Remote Services API, необходимо привести его к классу IRemoteServiceContainerAdapter с помощью вызова метода getAdapter(IRemoteServiceContainerAdapter.class).

Небольшое отступление. Использование такого механизма обеспечивает очень высокую гибкость. Основное свойство ECF заключается в том, что каждый контейнер может реализовывать несколько API, а каждый API быть реализовано несколькими контейнерами. Причем можно расширять как набор API, так и набор контейнеров. Поэтому контейнер реализует интерфейс IContainer и знать не знает об каких-то там API. Эта расширяемость как раз и обеспечивается за счет применения механизма адаптеров и их независимой регистрации.

После этого начинаются различия между хостовым бандлом и клиентским. Хостовый бандл регистрирует сервис с помощью вызова метода IRemoteServiceContainerAdapter#registerRemoteService, который получает на вход массив имен сервисов; экземпляр класса, реализующего сервис, и словарь свойств сервиса:

_context = context;



// Create R-OSGi Container

IContainerManager containerManager = getContainerManagerService();



_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");



// Get remote service container adapter

IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container

    .getAdapter(IRemoteServiceContainerAdapter.class);



// Register remote service

_serviceRegistration = containerAdapter.registerRemoteService(new String[] {IHello.class.getName()},

        new SimpleHello(), null);



System.out.println("IHello RemoteService registered");


Клиент же должен найти ссылки на заданные сервисы и обеспечить получение нужного.

public static final String ROSGI_SERVICE_HOST = "r-osgi://localhost:9278";



//...



_context = context;



// Create R-OSGi Container

IContainerManager containerManager = getContainerManagerService();

_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");



// Get remote service container adapter

IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container

       .getAdapter(IRemoteServiceContainerAdapter.class);



// Lookup IRemoteServiceReference

IRemoteServiceReference[] helloReferences = containerAdapter.getRemoteServiceReferences(IDFactory.getDefault().createID(_container.getConnectNamespace(), ROSGI_SERVICE_HOST), IHello.class.getName(), null);



// Get remote service for reference

IRemoteService remoteService = containerAdapter.getRemoteService(helloReferences[0]);

 


Видно, что осуществляется поиск сервисов в конкретном удаленном контейнере, имеющем конкретный адрес.

После получения сервиса его можно вызвать. Remote Service API позволяет вызывать сервис по-разному:

1. С помощью прокси

// Get the proxy

IHello proxy = (IHello) remoteService.getProxy();



// Call the proxy

proxy.hello("RemoteService Client via Proxy");

System.out.println((new Date()) + " RemoteService Called via Proxy");

 


2. Через синхронный вызов. "Вызов" в данном и нижеследующих случаях обозначает экземпляр класса, реализующего интерфейс IRemoteCall.

public void start(BundleContext context) throws Exception

{

    // Call Sync

    remoteService.callSync(createRemoteCall("RemoteService Client Sync"));

    System.out.println((new Date()) + " RemoteService Called Sync");

}



// ...



private IRemoteCall createRemoteCall(final String message)

{

    return new IRemoteCall()

    {

        @Override

        public String getMethod()

        {

            return "hello";

        }



        @Override

        public Object[] getParameters()

        {

            return new Object[] {message};

        }



        @Override

        public long getTimeout()

        {

            return 0;

        }

    };

}


3. Через асинхронный вызов. Напомню, что асинхронный вызов представляет собой команду, посылаемую сервису и обработчик, срабатывающий при возвращении сервисом значения. После отправки команды клиент может продолжать выполнять какой-то код.

Обработчик представляет собой объект класса, реализующего интерфейс IRemoteCallListener

public void start(BundleContext context) throws Exception

{

    // ...

    // Call Async

    remoteService.callAsync(createRemoteCall("RemoteService Client Async"), createRemoteCallListener());

    System.out.println((new Date()) + " RemoteService Called Async");

}



private IRemoteCallListener createRemoteCallListener()

{

    return new IRemoteCallListener()

    {

        @Override

        public void handleEvent(IRemoteCallEvent event)

        {

            if (event instanceof IRemoteCallCompleteEvent)

            {

                IRemoteCallCompleteEvent cce = (IRemoteCallCompleteEvent) event;

                if (!cce.hadException())

                    System.out.println("Remote call completed successfully!");

                else

                    System.out.println("Remote call completed with exception: " + cce.getException());

            }

        }

    };

}


4. Через IFuture. Данный механизм так же асинхронный, но отличается от предыдущего тем, что результат от сервиса мы получаем тогда, когда хотим. Если к тому времени результат еще не вычислен - наш поток блокируется.

// Call Async via IFuture

IFuture future = remoteService.callAsync(createRemoteCall("RemoteService Client Async via Future"));

System.out.println((new Date()) + " Create IFuture");

// ...

future.get();

System.out.println((new Date()) + " RemoteService Called via IFuture");


Для запуска нужно создать соответствующую конфигурацию, куда обязательно добавить следующие бандлы:

name.samolisov.ecf.hello
name.samolisov.ecf.remoteservices.rosgi.host (для клиента - .client Auto-Start: true)
org.eclipse.ecf
org.eclipse.ecf.sharedobject
org.eclipse.ecf.remoteservice
org.eclipse.ecf.identity
org.eclipse.ecf.discovery
org.eclipse.ecf.provider
org.eclipse.ecf.provider.jmdns
org.eclipse.ecf.provider.r_osgi
org.eclipse.ecf.ssl
ch.ethz.iks.r_osgi.remote
org.eclipse.core.jobs
org.eclipse.core.runtime.compatibility.registry
org.eclipse.equinox.common
org.eclipse.equinox.concurrent
org.eclipse.equinox.registry
org.eclipse.osgi
org.eclipse.osgi.services
org.objectweb.asm


Пара слов об использовании протокола, специально разработанного для ECF. Этот протокол представлен двумя видами контейнеров: ecf.generic.server и ecf.generic.client. Сервер, соответственно, используется в хостовом бандле, а клиент - в клиентском. При создании сервера можно задать его порт и т.н. группу (часть имени после последнего слэша):

public static final String GENERIC_SERVICE_HOST = "ecftcp://localhost:4280/mygroup";



// ...



// Create R-OSGi Container

ID serverId = IDFactory.getDefault().createStringID(GENERIC_SERVICE_HOST);

IContainerManager containerManager = getContainerManagerService();

_container = containerManager.getContainerFactory().createContainer("ecf.generic.server", serverId);


Чтобы при использовании generic в момент получения адаптера не возникло NPE, нужно немного изменить конфигурацию запуска - добавить туда бандл org.eclipse.ecf.provider.remoteservice. Относящиеся к R-OSGi бандлы (ch.ethz.iks.r_osgi.remote, org.objectweb.asm, org.eclipse.ecf.provider.jmdns, org.eclipse.ecf.provider.r_osgi) можно убрать.

Возможно, данная статья поможет вам разобраться с понятием remouting и заставит обратить внимание на соответствующие механизмы платформы Eclipse. В любом случае буду рад ответить на ваши вопросы.

Скачать примеры к статье (исходники, class-файлы и конфигурации запуска. Rar, 39 Кб)

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter

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

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

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!