среда, 11 ноября 2009 г.

ECF: Средства для взаимодействия команды разработчиков с помощью Eclipse


Мы уже успели поговорить и о том, что такое Eclipse Communication Framework, и о том, как программировать с его помощью. Сегодня я бы хотел рассказать о тех графических средствах, которые ECF предоставляет для Eclipse (и, соответственно, - ваших RCP-приложений).

Под графическими средствами подразумевается перспектива Communications и виды, которые представлены в группах Communications и File Transfer. Прежде всего эти компоненты GUI обеспечивают возможность взаимодействия разработчиков, использующих Eclipse между собой в команде. Однако, обо всем по порядку.

Давайте перейдем в перспективу Communications. По-умолчанию, внизу, под редактором кода, появятся два вида: Collaboration - обеспечивает общение между участниками проектной команды с использованием специально разработанного ECF-протокола: ecf.generic. Вот так он выглядит, пока соединение с сервером не установлено:



Рядом будет открыт вид Messages - окно сообщений. Здесь мы можем писать сообщения другим пользователям и, соответственно, читать поступившие от них. Пустой вид Messages представлен на рисунке:



На панели быстрого запуска мы можем увидеть кнопку Connect Workspace to Collaboration Group - позволяет соединиться с сервером и начать общение с проектной командой.



Кнопка Connect to Provider в свою очередь обеспечивает подключение с помощью выбранного ECF-провайдера. Например, можно подключиться к IRC, Jabber или MSN серверу.



Если нажать на кнопку Connect Workspace to Collaboration Group, то появится окошко выбора сервера, к которому мы хотим подключиться. Я предварительно запустил сервер на 4280-м порту и именно с ним осуществляю соединение:



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



Можно запустить еще один экземпляр Eclipse или, если есть возможность, воспользоваться другой машиной. Если писать сообщение в поле ввода, которое расположено внизу под окошком диалога, - оно будет отправлено всем участником команды:



Давайте откроем контекстное меню любого пользователя (но не себя). В нем расположены операции, которые можно производить с данным участником группы. Рассмотрим их подробнее.



Первый пункт: Send Screen Capture to ... позволяет отправить другому разработчику снимок выбранной области экрана. Например, как выглядит участок кода, текста в консоли или еще что-нибудь. Если выбрать данный пункт, то появится окошко-предупреждение, объясняющее, что сейчас надо делать.



Подождем 5 секунд и выделим участок кода. Нам зададут вопрос, а действительно мы хотим этот участок отправить или может быть мы шутим?



К сожалению для ECF, мы не шутим, и ему приходится отправлять выделенный участок кода:



Второй пункт: Co-Browse Web with .... Здесь все просто: мы вводим урл сайта и он открывается во встроенном браузере Eclipse. Причем, открывается как у нас, так и у пользователя, с которым мы ведем взаимодействие.

Третий пункт: Ring ... - показать всплывающее окно с сообщением. Выбираем его - перед нами появляется окошко для ввода сообщения:



Пишем текст сообщения, нажимаем ОК - сообщение всплывает перед глазами коллеги:



Четвертый пункт: Send Private Message to .... Понятно, что всплывающее сообщение сразу же отвлекает от работы, поэтому пользоваться данной опцией нужно аккуратно. К счастью, существует данный пункт меню. Если его выбрать - на вкладке Messages появится страничка с диалогом. Здесь можно отправлять и получать приватные сообщения от/к пользователя/ю.



Пятый пункт: Send Show View Request to .... Наиболее понравившаяся мне опция, с ее помощью мы отправляем коллеге запрос на открытие у него в Eclipse интересующего нас вида. Т.е., например мы хотим, чтобы он открыл вид Ant. Выбираем данный пункт меню - всплывает окно выбора вида:



Выбираем пункт Ant и он открывается в Eclipse у коллеги:



В общем-то это все, что хотелось сказать о пункте Collaboration. Теперь рассмотрим общение с помощью провайдеров, например подключимся к Jabber, используя свой аккаунт: выберем пункт XMPP, после чего заполним форму.



К сожалению, разработчики не предусмотрели автоматическое определение Jabber-сервера, поэтому его придется вводить руками. Нам вежливо напомнят, что аккаунту на Gmail соответствует сервер talk.google.com.

После подключения к серверу появится вкладка Contacts, на которой, соответственно, будут отображаться все наши контакты:



После того, как мы выберем конкретный контакт, на вкладке Messages откроется страница диалога:



Если контакт подключен к серверу так же с помощью ECF, то в его контекстном меню будут доступны опции Send Show View Request и Send Screen Capture, аналогичные описанным выше. Send File тоже работает.



Вид File Transfer/File Transfer представляет собой download-клиент. После нажатия на иконку Start new File Download откроется диалоговое окно выбора источника файла:



При нажатии на Download файл будет закачан.



Существует так же вид Connections/Chats, который представляет собой список, например, IRC-чатов. Если подключиться к IRC-серверу с помощью соответствующего провайдера, то данный вид откроется и будет активным:



И, наконец, вид Connections/Service Discovery предоставляет нам дерево запущенных удаленных OSGi-сервисов. Показываются все обнаруженные сервисы, включая и запущенные на локальной машине. В случае одного запущенного R-OSGi-сервиса это окно выглядит так:



Понятно, что данное представление существенно помогает при отладке взаимодействия в распределенной OSGi-среде.

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

А вы собираетесь использовать средства, предоставляемые ECF в своей практике? Я имею ввиду как программные абстракции, так и Collaboration/Service Discovery/Providers.

З.Ы. Недавно я начал контрибьютить в ECF - разрабатываю провайдер для взаимодействия по протоколу OSCAR/ICQ с использованием caffeineim. Если вы используете данную библиотеку - хорошая новость для вас: я продолжу ее поддерживать.

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


А дальше...

суббота, 7 ноября 2009 г.

ECF: Распределяем объекты между OSGi-фреймворками


Сегодня мы рассмотрим еще одну замечательную возможность, которую предоставляет нам Eclipse Communication Framework - обмен копиями объектов между бандлами, запущенными на разных экземплярах OSGi-фреймворка (т.е. на разных JVM), реализованную в виде SharedObject API. Данный механизм основан на понятии "репликация", суть которого применительно к ECF следующая: в контейнер добавляется экземпляр класса, реализующего интерфейс ISharedObject, который становится доступен всем другим контейнерам, подключенным к тому же серверу, что и донор. Фактически, в каждом клиентском контейнере создаются копии исходного объекта, которые называются репликами.

Прежде всего разберемся непосредственно с распределяемым объектом. В ECF реализован базовый класс BaseSharedObject, который содержит реализацию многих полезных возможностей, таких как идентификация, управление конфигурацией, логгирование, отправка и прием сообщений. Рекомендуется все свои распределяемые объекты наследовать от данного класса.

Наиболее интересными для нас методами являются BaseSharedObject#initialize(), описывающий стратегию репликации главного объекта (т.е. того объекта, который собственно копируется, для него метод BaseSharedObject#isPrimary() вернет true) и стратегию десериализации реплик из словаря свойств, и BaseSharedObject#getReplicaDescription(ID receiver), задающий порядок сериализации реплицируемого объекта в словарь свойств. Словарь свойств - это объект класса, реализующего Map<String, Serializable>. ECF умеет передавать объекты между контейнерами только в таком виде. Чем-то данный механизм напоминает словарь свойств события, которым оперирует EventAdmin. Данный механизм предоставляет программисту возможность наиболее гибко управлять процессом репликации своего объекта (например, определять какие поля не нужно копировать вовсе).

Стоит обратить внимание, что каждое реплицируемое поле класса должно иметь тип, реализующий интерфейс Serializable. Дело в том, что для сериализации полей класса ECF использует стандартные средства Java-платформы.

Давайте рассмотрим пример метода initialize():

    /* (non-Javadoc)

     * @see org.eclipse.ecf.core.sharedobject.BaseSharedObject#initialize()

     */


    protected void initialize() throws SharedObjectInitException

    {

        super.initialize();



        if (isPrimary())

        {

            // If primary, then add an event processor that handles activated

            // event by replicating to all current remote containers

            addEventProcessor(new IEventProcessor()

                {

                    public boolean processEvent(Event event)

                    {

                        if (event instanceof ISharedObjectActivatedEvent)

                        {

                            ISharedObjectActivatedEvent activated = (ISharedObjectActivatedEvent) event;

                            if (activated.getActivatedID().equals(getID()) && isConnected())

                            {

                                MySharedObject.this.replicateToRemoteContainers(null);

                            }

                        }

                        return false;

                    }

                });



            System.out.println("Primary(" + getContext().getLocalContainerID().getName() + ")");

        }

        else

        {

            // This is a replica, so deserialize properties from HashMap to Object

            _name = (String) getConfig().getProperties().get(NAME_PROPERTY);

            _property = (DemoProperty) getConfig().getProperties().get(PROPERTY_PROPERTY);

            System.out.println("Replica(" + getContext().getLocalContainerID().getName() + ")");

        }

    }


Чтобы реплицировать главный экземпляр SharedObject'а, мы регистрируем свой обработчик события ISharedObjectActivatedEvent. Обработчик достаточно прост: мы убеждаемся, что действие происходит именно с нами и вызываем метод replicateToRemoteContainers. Вообще, данный метод принимает массив удаленных контейнеров, которым мы хотим отправить наш объект. В случае, если передается null, - объект отправляется всем контейнерам, с которыми можно установить соединение.

Если же перед нами не главный объект, а его реплика, то мы извлекаем из конфигурации нужные поля и строим из них копию объекта (т.е. производим десериализацию).

Пример метода getReplicaDescription:

    /* (non-Javadoc)

     * @see org.eclipse.ecf.core.sharedobject.BaseSharedObject#getReplicaDescription(org.eclipse.ecf.core.identity.ID)

     */


    protected ReplicaSharedObjectDescription getReplicaDescription(ID receiver)

    {

        System.out.println("getReplicaDescription(receiver = " + receiver + ")");



        // Put primary state into properties and include in replica description

        // This is serialization SharedObject into HashMap

        Map<String, Serializable> properties = new HashMap<String, Serializable>();

        properties.put(NAME_PROPERTY, _name);

        properties.put(PROPERTY_PROPERTY, _property);

        return new ReplicaSharedObjectDescription(this.getClass(), getConfig().getSharedObjectID(),

                getConfig().getHomeContainerID(), properties);

    }

 


В качестве параметра в метод передается ID контейнера-получателя. Таким образом обеспечивается возможность строить сериализованное представление объекта в зависимости от ID получателя. Впрочем, в данном примере мы эту возможность не используем. В целом код метода тривиален: строим словарь параметров, по которому создаем ReplicaSharedObjectDescription.

Теперь научимся всем этим пользоваться. Прежде всего - рассмотрим процедуру добавления в контейнер распределяемого объекта. Общий алгоритм такой:

1. Создаем контейнер, тип которого реализует SharedObject API:

public static final String GENERIC_CLIENT_CONTAINER = "ecf.generic.client";

// ...

_client = ContainerFactory.getDefault().createContainer(GENERIC_CLIENT_CONTAINER);


2. Подключаемся к серверу:

private void connectClient() throws Exception

{

    connectClient(_client, createServerID(), null);

}



private ID createServerID() throws Exception

{

    return createNewID(GENERIC_SERVER_ID);

}



private void connectClient(IContainer containerToConnect, ID connectID, IConnectContext context)

        throws ContainerConnectException

{

    containerToConnect.connect(connectID, context);

}



private ID createNewID(String id)

{

    return IDFactory.getDefault().createStringID(id);

}


3. Добавляем распределяемый объект в контейнер:

private ISharedObjectManager getClientSOManager()

{

    return ((ISharedObjectContainer) _client).getSharedObjectManager();

}



// ...



ISharedObjectManager manager = getClientSOManager();

ID id = manager.addSharedObject(createNewID(OBJECT_ID),

    new MySharedObject(OBJECT_NAME, OBJECT_PROPERTY_ID, OBJECT_PROPERTY_A, OBJECT_PROPERTY_B), null);

System.out.println("Added new SharedObject with ID = " + id.getName());


Все взаимодействие распределяемого объекта и контейнера осуществляется посредством ISharedObjectManager. Данный интерфейс содержит методы для добавления, создания, удаления, подключения, отключения и получения распределяемых объектов. Код интерфейса достаточно хорошо документирован, поэтому останавливаться подробно на нем не будем.

Теперь рассмотрим процедуру получения реплики объекта и работы с нею. Вообще, стоит отметить, что SharedObject API подразумевает асинхронное взаимодействие. Т.е. в контейнере, который будет работать с репликами объектов, регистрируется обработчик события ISharedObjectActivatedEvent. Именно в этом обработчике и должен находиться код, принимающий копию распределяемого объекта. Например, такой:

if (event instanceof ISharedObjectActivatedEvent)

{

    try

    {

        ISharedObjectActivatedEvent ae = (ISharedObjectActivatedEvent) event;

        System.out.println("Took shared object with id = " + ae.getActivatedID().getName()

                + " to " + ae.getLocalContainerID().getName());

        ISharedObjectManager manager = getClientSOManager(index);

        ISharedObject sharedObject = manager.getSharedObject(ae.getActivatedID());

        System.out.println("Shared Object class = " + sharedObject.getClass().getName());



        if (sharedObject instanceof MySharedObject)

        {

            MySharedObject my = (MySharedObject) sharedObject;

            System.out.println("Name = " + my.getName());

            System.out.println("Demo = " + my.getProperty());

        }

    }

    catch (Exception e)

    {

        e.printStackTrace();

    }

}


Здесь мы получаем из контейнера экземпляр распределяемого объекта и выводим в OSGi-консоль значения его свойств.

Еще одной интересной возможностью является обмен сообщениями между объектом и его репликами. Такой обмен можно использовать, например, для синхронизации состояния объектов. В примерах к статье приведен код класса MyMessagingSharedObject, демонстрирующего использование данного механизма. Стоит отметить, что отправка сообщения осуществляется посредством вызова метода BaseSharedObject#sendSharedObjectMsgTo, который принимает в качестве параметров ID контейнера-назначения (если равен null - то сообщение будет отправлено всем контейнерам), и сообщение - объект класса SharedObjectMsg, который можно построить с помощью целого ряда методов SharedObjectMsg#createMsg.

Код отправки сообщения может быть таким:

public void sendMessage(ID targetId, Object message) throws IOException

{

    sendSharedObjectMsgTo(null, SharedObjectMsg.createMsg(this.getClass().getName(),

        "handleMessage", new Object[] {getLocalContainerID(), message}));

}


"handleMessage" - имя метода класса распределяемого объекта, который будет вызван при получении сообщения. Обмен сообщениями, как и обмен объектами, - асинхронный.

ECF позволяет управлять механизмом сериализации/десериализации посылаемых/принимаемых сообщений. Для регистрации своего сериализатора/десериализатора необходимо вызвать метод ISharedObjectContainer#setSharedObjectMessageSerializer. Сделать это можно, например, при создании контейнера:

private void createClient() throws Exception

{

    _client = ContainerFactory.getDefault().createContainer(GENERIC_CLIENT_CONTAINER);

    ((ISharedObjectContainer) _client).setSharedObjectMessageSerializer(new XStreamSOMessageSerializer());

}


В примерах приведен код класса XStreamSOMessageSerializer, который использует библиотеку XStream для отправки сообщений в XML-формате.

Главное - необходимо обеспечить единый механизм сериализации/десериализации на всем пути прохождения сообщения, т.е. и источник, и сервер, и клиент должны зарегистрировать один и тот же сериализатор.

Статья получилась объемная и несколько сумбурная. Если у вас возникли какие-то вопросы - вы всегда можете задать их в комментариях. В следующий раз я постараюсь написать о тех графических средствах, которые предоставляет ECF.

Оставайтесь на связи.

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

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


А дальше...

понедельник, 26 октября 2009 г.

ECF: Обмен данными между бандлами с помощью DataShare API


Проголосуйте за ролик первой Российской команды, принявшей участие в конкурсе Imagine Cup Digital Media.

Суровый челябинский программист снова с вами и сегодня мы поговорим об ином аспекте взаимодействия бандлов нежели вызов сервисов - об обмене сообщениями. Под сообщением в данном случае подразумевается произвольный поток байт. Для обеспечения такого взаимодействия в состав ECF входит DataShare API.

Суть использования данного API заключается в том, что каждый контейнер может создавать сколь угодно много каналов. Каналы с одинаковыми ID являются связанными и по ним осуществляется асинхронный обмен сообщениями. Асинхронность в данном случае обозначает, что контейнер при создании канала регистрирует для этого канала листенер. При получении сообщений от других контейнеров данный листенер срабатывает. Все свободное от обработки сообщений время контейнер может заниматься своими делами.

Важной особенностью является то, что контейнер-получатель не обязан уведомлять отправителя о поступлении сообщения. Конкретно в API никаких специальных мер для этого не предусмотрено. Однако, никто не мешает отправить такое уведомление самостоятельно.

В качестве примера рассмотрим использование DataShare API с помощью контейнеров семейства ecf.generic и контейнера ecf.xmpp.smack, реализующего соединение по протоколу XMPP (напомню, что данный протокол используется в Jabber).

Оба этих типа контейнеров (ecf.generic.client и ecf.xmpp.smack) не могут взаимодействовать напрямую, а только посредством сервера. В случае ecf.xmpp.smak сервером является любой XMPP-сервер, например gmail.com, ya.ru и т.д. В случае ecf.generic.client сервером является контейнер типа ecf.generic.server, который надо предварительно создать:

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



public static final String GENERIC_SERVER_CONTAINER = "ecf.generic.server";





private IContainer createServer() throws Exception

{

    return ContainerFactory.getDefault().createContainer(GENERIC_SERVER_CONTAINER, getNewID(GENERIC_SERVER_ID));

}



public void start(BundleContext context) throws Exception

{

    _container = createServer();

    // ...

}


Метод gentNewID(String id) будет использоваться во всех примерах, код его тривиален:

private ID getNewID(String id) throws IDCreateException

{

    return IDFactory.getDefault().createStringID(id);

}


С сервером все понятно. В дальнейшем мы к нему еще вернемся, чтобы показать как можно расширить его возможности. Сейчас же поговорим о клиентах, которые будут обмениваться друг с другом сообщениями.

Общая последовательность работы с DataShare API такова:

1. Создаем контейнеры (например, ecf.generic.client

public static final String GENERIC_CLIENT_CONTAINER = "ecf.generic.client";



//...



containers = new IContainer[CLIENTS_CNT];



for (int i = 0; i < containers.length; i++)

    containers[i] = ContainerFactory.getDefault().createContainer(GENERIC_CLIENT_CONTAINER);

 


2. Создаем для контейнеров каналы, если каналы будут иметь одинаковый ID, то они будут связанными, т.е. по ним можно будет рассылать данные.

public static final String CHANNEL_NAME = "channel";



for (int i = 0; i < CLIENTS_CNT; i++)

{

    IChannelContainerAdapter channelContainer = (IChannelContainerAdapter) containers[i]

                    .getAdapter(IChannelContainerAdapter.class);

    channelContainer.createChannel(getNewID(CHANNEL_NAME), getChannelListener(containers[i].getID()), null);

}


Один контейнер может иметь только один канал с заданным ID. Т.е. нельзя регистрировать два канала с одним и тем же ID для одного и того же контейнера.

Для создания канала используется IChannelContainerAdapter. Чтобы при создании данного адаптера не было NPE, необходимо присутствие бандла org.eclipse.ecf.provider.datashare в конфигурации запуска.

IChannelListener - интерфейс слушателя, срабатывающего при получении сообщения. Он может иметь сколь угодно сложную реализацию, мы же ограничимся сохранением события в Map:

private Hashtable<ID, IChannelEvent> messageEvents = new Hashtable<ID, IChannelEvent>();



private IChannelListener getChannelListener(final ID id) throws Exception

{

    return new IChannelListener()

    {

        public void handleChannelEvent(IChannelEvent event)

        {

            if (event instanceof IChannelMessageEvent)

            {

                messageEvents.put(id, event);

            }

        }

    };

}


3. Подключаемся к серверу.

Подключение к серверу осуществляется вызовом метода connect контейнера клиента. Данный метод получает два параметра: ID сервера и так называемый контекст соединения. Контекст соединения может служить, например, для авторизации. В нашем случае авторизация не нужна, поэтому можно передать null.

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



private void connectClients() throws Exception

{

    for (int i = 0; i < containers.length; i++)

        connectClient(containers[i], IDFactory.getDefault().createStringID(GENERIC_SERVER_ID), getConnectContext(i));

}



private IConnectContext getConnectContext(int index)

{

    return null;

}



private void connectClient(IContainer containerToConnect, ID connectID, IConnectContext context)

        throws ContainerConnectException

{

    containerToConnect.connect(connectID, context);

}

 


4. Отправляем сообщение.

Сообщение отправляется с помощью метода IChannel#sendMessage, который может быть вызван с одним обязательным параметром: массивом байт, который будет передан по каналу. В таком случае этот массив байт получат все контейнеры, которые создали канал с соответствующим ID. Другим вариантом может быть вызов метода IChannel#sendMessage с переданным в качестве первого параметра ID контейнера-получателя. В таком случае, сообщение будет передано только этому контейнеру.

private void sendMessages() throws IDCreateException, Exception

{

    IChannelContainerAdapter senderContainer = getChannelContainer(0);



    IChannel sender = senderContainer.getChannel(getNewID(CHANNEL_NAME));

    sender.sendMessage("Hello from ECF!".getBytes());



    sleep(3000);



    for (int i = 1; i < CLIENTS_CNT; i++)

        handleEvent(containers[i].getID());

}



private void handleEvent(ID id)

{

    IChannelEvent event = messageEvents.get(id);

    if (event instanceof IChannelMessageEvent)

    {

        System.out.println("chanelID = " + event.getChannelID().getName());

        System.out.println("data = " + new String(((IChannelMessageEvent) event).getData()));

        System.out.println();

    }

}


Теперь вернемся к серверу. Во-первых, сервер имеет возможность обрабатывать момент соединения с ним клиента. Для этого нужно добавить к контейнеру сервера обработчик события IContainerConnectedEvent. Для добавления обработчиков событий у IContainer есть метод addListener:

// ...

_container.addListener(createConnectedContainerListener());

// ...



private IContainerListener createConnectedContainerListener()

{

    return new IContainerListener()

    {

        public void handleEvent(IContainerEvent event)

        {

            if (event instanceof IContainerConnectedEvent)

            {

                try

                {

                    ID containerId = ((IContainerConnectedEvent) event).getTargetID);

                    System.out.println("connected from " + containerId.getName());

                }

                catch (Exception e)

                {

                    e.printStackTrace();

                }

            }

        }

    };

}


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

Во-вторых, сервер может как получать сообщения от клиента, так и посылать их ему. Для этого можно в момент соединения создать канал для связи клиент-сервер. Клиент может по этому каналу отправить сообщение на которое сервер ответит (см. примеры к статье).

Интересным примером может быть организация обмена данными с помощью XMPP-протокола. Такой вариант имеет свои особенности.

1. ID сервера задается в пространстве имен ecf.xmpp и представляет собой корректный Jabber Account ID, который будет использоваться.

public static final String XMPP_NAMESPACE = "ecf.xmpp";



// ...

return IDFactory.getDefault().createID(IDFactory.getDefault().getNamespaceByName(XMPP_NAMESPACE),

    "samolisov@gmail.com");


2. При соединении обязательно нужно передать контекст соединения, т.е. пользовательскую пару логин/пароль. Создается контекст соединения следующим образом:

ConnectContextFactory.createUsernamePasswordConnectContext(username, password);


3. ID канала задается в пространстве имен по-умолчанию и может называться как угодно, например, просто channel.

4. При отправке сообщения необходимо обязательно указывать ID получателя, заданное относительно пространства имен ecf.xmpp. Чтобы не было проблем, нужно указывать полный уникальный ID ресурса (т.е. что-то вроде "samolisov@gmail.com/Gajim12345"). Я сделал следующую ошибку: не указал ID ресурса, указал только samolisov@gmail.com, но у меня было запущенно два Джаббер-клиента, в итоге сервер (gmail.com) не смог выбрать куда ему отправить сообщение.

5. Вполне допускается и корректно работает отправка данных между разными Jabber-серверами. Однако, gmail.com, например, не позволяет отправлять сообщение на JID, которого нет в ростере. Поэтому перед тестированием/использованием добавьте JID получателя в ростер отправителя.

6. Существует замечательный инструмент отладки. Если при запуске вашего приложения указать Java-машине параметр -Dsmack.debugEnabled=true, то будет показано окно, отображающее все XMPP-пакеты, проходящие через ваш аккаунт.

Тема взаимодействия бандлов очень интересна. ECF позволяет не только обмениваться потоками байт, но и произвольными Java-объектами, однако, об этом мы поговорим в другой раз.

Оставайтесь на связи.

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

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


А дальше...

суббота, 24 октября 2009 г.

Два года блогу Сурового челябинского программиста!




Здравствуй, мой дневничок. Поздравляю тебя со знаменательной датой - твоим вторым днем рождения. Ровно два года назад я, работая еще в Capital-IT, завел тебя, чтобы поиграться с платформой blogspot, но кто же знал, что все зайдет так далеко.

А ведь действительно, далеко все зашло. Нет, я еще пока не тысячник, но сегодня на счетчике виднеется число 634, которое меня очень радует. Что особо интересно, начиная с мая, рост числа подписчиков ускоряется (после какого-то порогового значения этот маховик начинает раскручивать сам себя). Для примера: первую сотню я набирал год, вторую - 7 месяцев, третью - 4 месяца, а шестую - 3 недели.

Радует не только рост числа на счетчике. Благодаря блогу я познакомился с очень интересными людьми. Так же смог систематизировать имеющиеся у меня знания, научиться писать и выступать. Работа над блогом позволила мне изучить довольно интересные технологии, с которыми иначе я бы не познакомился. Ну и из приятных бонусов - футболка от Eclipse Foundation и, в конце концов, - наберите в гугле "Суровый челябинский программист" :)

Здесь стоит отметить и поблагодарить ресурсы и их авторов/владельцев, которые абсолютно бесплатно приводят мне посетителей:

- Google - основной трафик идет по поисковым запросам. Самый "брендовый" запрос: "Суровый челябинский программист". Самыми курьезными запросами были: "ошибка t1 на ризографе", "скачать naumen dms", "почему надо переходить на линукс", "блог конкурс приз", "суровый шрифт", "мой блог".



- ХабраХабр - основной источник непоискового трафика. Когда я разместил на нем ссылку Что нового в Eclipse Galileo, то за день пришло 3000 уникальных посетителей. Стоит заметить, что на Хабре я чуть больше года и уже пригласил туда 8 человек.

- Блог о разработке Enterprise-приложений на Java - относительно недавно там был размещен интересный обзор русскоязычныx Java-блогов, откуда и идет большая часть переходов. Рекомендую подписаться на этот блог, его автор хорошо разбирается в том, что называется J2EE и сопутствующих технологиях.

- Progg - преимущественно русскоязычная социальная сеть, напоминающая Digg для программистов. Можно запостить туда ссылку на свою заметку и, если она понравится - за нее будут голосовать.

- Eclipse - да, на официальном сайте Eclipse тоже есть ссылка на мой блог.

- Как можно больше об Eclipse по-русски - блог об Eclipse и всем, что с ним связано. В частности, там присутствует замечательное описание Eclipse Modeling Framework.

- Записки дебианщика - блог о GNU/Linux (естественно, как и отражено в названии - в основном о Debian GNU/Linux). Там можно найти сведения о настройке тех или иных программ и сервисов, а также о верстве диплома в LaTeX.

Интересно, но из социальной сети "ВКонтакте" переходов очень мало, так же как и из Twitter.

Пару слов о том, зачем это мне надо. Я долго думал над этом и решил, как бы пафосно это ни звучало, что я - патриот. Да, сейчас это не модно (амикошонство какое, фи), всячески осмеиваемо (к сожалению, и людьми, которых я считаю думающими), но так я воспитан. Естественно, на благословенном Западе все всегда было, есть и будет лучше, чем в сами знаете какой "Рашке", которая катится сами знаете куда (почти (с)), но я хочу, чтобы программисты из России могли иметь доступ к информации на русском языке и пытаюсь в меру своих сил и способностей эту информацию предоставить. Как у меня это получается - судить вам, читателям.

На последок поделюсь планами на будущий год. В следующем году постараюсь полнее освещать свою научную работу в сфере моделирования/исполнения бизнес-процессов и распределенных систем. Продолжу писать про OSGi, ECF и Eclipse Platform. Начну серию статей о BPEL. Так же, вероятно (пока не уверен), напишу что-нибудь про доверенный документооборот.

В общем, думаю, будет интересно. Оставайтесь на связи :)

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


А дальше...

пятница, 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


А дальше...

вторник, 20 октября 2009 г.

IAdaptable - одно из основных понятий Eclipse Core


Как я и обещал, постепенно от введения в OSGi мы переходим в сторону рассмотрения непосредственно Eclipse Platform. Впрочем, об Eclipse Rich Client Platform (или даже Eclipse Rich Ajax Platform) речь пока не идет, пока будем знакомиться только с Eclipse Core. Дело в том, что для последующего рассмотрения возможностей того же Eclipse Communication Framework, невозможно оставаться только в рамках платформы OSGi.

Впрочем, это еще не значит, что я не буду больше писать про OSGi вообще (в том числе и про Equinox, как одну из реализаций OSGi).

Давайте, прежде чем перейти к рассмотрению IAdaptable, скажем пару слов об Eclipse Core (в дальнейшем я иногда буду называть его Ядром).

Ядро состоит из следующих основных бандлов:

org.eclipse.core.contenttype - поддерживает определение и управление типами файлового контента (в частности - MIME-типами).

org.eclipse.core.expression - содержит реализацию основанного на XML языка выражений, который используется для декларативного определения точек расширения.

org.eclipse.core.filesystem - общее API работы с файловой системой.

org.eclipse.core.jobs - обеспечивает инфраструктуру параллельного исполнения кода для Eclipse

org.eclipse.core.resource - содержит средства управления ресурсами: проектами, каталогами и файлами.

org.eclipse.core.runtime - обеспечивает основанный на Equinox рантайм для работы всех остальных бандлов и приложений. Является сердцем Eclipse Core.

Так же к Ядру можно отнести org.eclipse.equinox.registry - определяет так называемый реестр точек расширения. Точки расширения - специальные записи на XML-подобном языке, определяющие механизм взаимодействия бандлов. Используются еще с тех пор, когда Eclipse не был основан на OSGi. Как-нибудь поговорим о точках расширения подробнее.

Теперь можно перейти непосредственно к разговору об IAdaptable, определенному в бандле org.eclipse.core.runtime и являющемуся одним из краеугольных механизмов всей платформы.

Как известно, Java - язык со строгой типизацией, т.е. каждый объект имеет связанный с ним тип. Существует два вида типов: тип времени определения переменной и тип времени ее исполнения. Чтобы можно было вызвать некоторый метод, он должен присутствовать в типе времени определения. Для примера рассмотрим такой код:

List list = new ArrayList();

list.add("data");       // this is OK, list is valid

list.ensureCapacity(4); // this is not, ensureCapacity() is ArrayList only


Чтобы вызвать метод, определенный в классе ArrayList, нужно сделать приведение типов. Так как в данном случае List реализуется классом ArrayList и мы это знаем, то можно воспользоваться стандартными средствами Java:

((ArrayList) list).ensureCapacity(4);


Можно даже проверить, является ли list переменной типа ArrayList с помощью instanceof. Но как поступить, если нам нужно сделать приведение между несвязанными типами? Например мы хотим привести переменную, реализующую интерфейс Map к типу List? Здесь нам на помощь и приходит IAdaptable:

IAdaptable adaptable = new HashMap();

List list = (List)adaptable.getAdapter(List.class);


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

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

@SuppressWarnings("unchecked")

    @Override

    public Object getAdapter(Class clazz) {

        if (clazz == List.class)

        {

            List list = new ArrayList(this.size());

            list.addAll(this.values());

            return list;

        }



                                               // ...



        return null;

    }


Если ни одно условие не срабатывает (т.е. запросили приведение к неподдерживаемому типу), то нужно возвратить null. Это кстати определяет и использование IAdaptable: не стоит забывать, что может быть возвращен и null.

Получается, что если мы хотим добавить новый адаптер, т.е. обеспечить приведение интересующего нас класса к еще какому-нибудь типу, то нам нужно вносить изменения в этот класс. Это не всегда возможно (у нас может и не быть исходников этого класса) и/или вредно с архитектурной точки зрения (в частности - приведение сущности к GUI-типу, в этом случае в класс сущности нужно добавить ссылки на GUI-тип, что приведет к невозможности использовать сущность без GUI). Однако, в Eclipse Core есть решение этой проблемы: PlatformObject.

PlatformObject - абстрактный класс, реализующий интерфейс IAdaptable и содержащий следующий метод getAdapter:

@Override

public Object getAdapter(Class clazz) {

        return Platform.getAdapterManager().getAdapter(this, clazz);

}

   


AdapterManager представляет собой большой Map в котором для каждого класса хранятся его адаптеры. Адаптеры создаются с помощью фабрик, реализующих интерфейс IAdapterFactory, которые регистрируются в AdapterManager. Регистрироваться фабрики могут или в активаторе бандла, или через точку расширения org.eclipse.core.runtime.adapters.

Давайте напишем фабрику, создающую адаптер для приведения типа Map к типу List:

package name.samolisov.eclipse.adaptersdemo.internal;



import java.util.ArrayList;

import java.util.List;

import java.util.Map;



import org.eclipse.core.runtime.IAdapterFactory;



public class MapListAdapterFactory implements IAdapterFactory {



    @SuppressWarnings("unchecked")

    private static final Class[] types = {List.class};



    @Override

    @SuppressWarnings("unchecked")

    public Object getAdapter(Object obj, Class clazz) {

        if (clazz == List.class && obj instanceof Map) {

            List result = new ArrayList();

            result.addAll(((Map) obj).values());

            return result;

        }



        return null;

    }



    @Override

    @SuppressWarnings("unchecked")

    public Class[] getAdapterList() {

        return types;

    }

}

 


Метод getAdapterList возвращает массив классов, в которые осуществляется преобразование. Метод getAdapter реализует непосредственно логику преобразования объекта obj в объект класса clazz.

Кстати, у нас ведь нет адаптируемого (т.е., реализующего интерфейс IAdaptable) класса, который бы реализовывал интерфейс Map. Самое время его написать:

package name.samolisov.eclipse.adaptersdemo;



import java.util.Collection;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;



import org.eclipse.core.runtime.PlatformObject;



public class AdaptableHashMap<K, V> extends PlatformObject implements Map<K, V> {



    private Map<K, V> delegate = new HashMap<K, V>();



    @Override

    public void clear() {

        delegate.clear();

    }



    @Override

    public boolean containsKey(Object key) {

        return delegate.containsKey(key);

    }



    // ...

}

 


Теперь рассмотрим пример регистрации адаптера и его использования в активаторе бандла:

Platform.getAdapterManager().registerAdapters(new MapListAdapterFactory(), Map.class);      



AdaptableHashMap<String, String> map = new AdaptableHashMap<String, String>();

map.put("red", "#FF0000");

map.put("blue", "#0000FF");

map.put("yellow", "#FFFF00");

map.put("gray", "#C0C0C0");



List<String> result = (List) map.getAdapter(List.class);

for (String str : result)

    System.out.println(str);


Метод registerAdapters регистрирует фабрику адаптеров для класса из которого будет совершаться преобразование. Для эксперимента можете убрать эту строчку с регистрацией фабрики - при запуске получите NPE, т.к. result будет равен null.

Манифест бандла может быть следующим:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Adaptersdemo

Bundle-SymbolicName: name.samolisov.eclipse.adaptersdemo

Bundle-Version: 1.0.0.qualifier

Bundle-Activator: name.samolisov.eclipse.adaptersdemo.Activator

Bundle-ActivationPolicy: lazy

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Import-Package: org.eclipse.core.runtime;version="3.4.0",

 org.osgi.framework;version="1.3.0"


Главное здесь - определить импорт пакета org.eclipse.core.runtime.

Возникает вопрос: а почему IAdaptable так важен для платформы Eclipse? Дело в том, что это - один из основных способов уменьшения связанности (coupling) между частями системы. В частности, при разработке плагинов для Eclipse принято отделять интерфейсные бандлы от бандлов бизнес-логики. Использование IAdaptable совместно с IAdapterManager и IAdapterFactory позволяет ввести промежуточный слой, который полностью отделяет сущности от классов GUI. В частности, мы можем определить список каких-то объектов (например - файлов) и написать адаптер для преобразования его в выпадающий список или красивое дерево. Причем, т.к. файл наследуется от PlatformObject, то он ничего не будет знать об этом адаптере, а значит и о выпадающем списке, что позволяет использовать его вне GUI. Дальше можно поиграться с вынесением адаптеров в отдельный бандл и регистрацией их в его активаторе.

Код примеров частично взят из статьи What is IAdaptable? (кстати, если вы хотите быть в курсе последних новостей из мира Eclipse и OSGi - рекомендую блог и твиттер автора).

Скачать примеры к статье (исходники, class-файлы и библиотеки. Zip, 3.8 Mб)

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


А дальше...

суббота, 17 октября 2009 г.

ECF: Разбираемся с R-OSGi


В недавно вышедшей спецификации 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.helloEclipse я создаю бандлы, как Plug-In проекты, при этом автоматически генерируется манифест, можно быстро экспортировать бандл в jar и легко запустить в OSGi-фреймворке). В данном бандле определим интерфейс нашего сервиса, назовем его IHello:

package name.samolisov.ecf.hello;



public interface IHello {



    public void hello(String from);

}

 


Напишем простую реализацию, в которой будем выводить на консоль приветствие и его автора:

package name.samolisov.ecf.hello;



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

 


Данный бандл мы будем использовать для того, чтобы сделать доступным 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();

    }

}

 


Как видно, этот код один в один реализует описанный выше алгоритм выставления распределяемого сервиса.

Манифест бандла следующий:

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"

 


Стоит подробно остановиться на запуске бандла и самое главное - зависимостях. Чтобы подготовить конфигурацию запуска, необходимо в контекстном меню проекта выбрать 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

 


Команда 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]


Теперь нужно создать бандл-клиент. Назовем его 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();

    }

}

 


В методе 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"

 


Теперь создадим конфигурацию запуска бандла. Войдем в 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


В то же время в консоль хостовой 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]


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();

            }

    }

}

 


С помощью метода 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();

        }


Интерфейс 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 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


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

Существует так же другой способ обеспечить асинхронный вызов. Этот способ - использование механизма 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();

}


Запустим измененый бандл (при запущенном хосте, естественно). Увидим в 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: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


Т.е. видно, что клиент был заблокирован в момент вызова 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);

    }

}

 


Так же определим класс 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);

    }

}

 


В активаторе бандла эти сервисы регистрируются следующим образом:

    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);

    }

 


Манифест бандла:

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"

 


Далее нужно настроить конфигурацию запуска. Добавим данный бандл в конфигурацию запуска 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


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

Создание бандла, реализующего IProxyDistributionListener и IProxyDiscoveryListener, оставляю вам в качестве упражнения.

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

Скачать примеры к статье (исходники, class-файлы, launch'еры. Rar, 33кб)

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


А дальше...