Продолжаем беседу об Eclipse Communication Framework. Сегодня мы рассмотрим, как использовать появившийся в версии 3.1 REST API на примере взаимодействия с наиболее популярным сервисом микроблогов - с Twitter.
Поддержка REST была добавлена в ECF этой осенью горячим немецким парнем по имени Holger Staudacher в рамках Google Summer of Code (который я, кстати, считаю гораздо более полезным, нежели Imagine Cup от небезызвестной корпорации). Реализация данной функциональности представленна контейнером ecf.rest.client и базируется на Remote services API.
Основными понятиями ECF REST API являются:
1. IRestParameter - интерфейс, описывающий параметр удаленного вызова. Позволяет задать имя параметра и его значение по-умолчанию. Объект, реализующий данный интерфейс, можно сконструировать вызовом одного из методов RestParameterFactory#createParameters.
2. IRestCallable - интерфейс, описывающий непосредственно вызов удаленного сервиса. Реализуется классом RestCallable, инкапсулирующим имя вызова, которое будет использоваться в ECF, путь к удаленному сервису (в терминах REST - URI), массив параметров (IRestParameter[]) и HTTP-метод. Фактически - мы обращение по каждому интересующему нас URI должны обернуть в свой IRestCallable.
3. IRestCall - наследник IRemoteCall, обеспечивающий вызов ранее зарегистрированного IRestCallable. Объекты, реализующие IRestCall создаются с помощью фабрики RestCallFactory.
4. IRestResourceProcessor - интерфейс, представляющий собой обработчик ответа от REST-сервиса. Содержит метод createResponseRepresentation, получающий экземпляр IRemoteCall, IRestCallable, мэп HTTP-заголовков и строку ответа от сервера. Возвращает данный метод объект, инкапсулирующий в себе ответ от сервера в том представлении, которое будет использоваться в разрабатываемой системе. По-умолчанию используется класс XMLResource, создающий по полученному ответу (предполагается, что ответ будет в формате XML) объект класса Document.
Порядок создания REST-клиента следующий:
1. Создаем контейнер типа ecf.rest.client
private static final String REST_CONTAINER_TYPE = "ecf.rest.client";
private static final String TWITTER_TARGET = "http://twitter.com";
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer(REST_CONTAINER_TYPE,
getRestID(TWITTER_TARGET));
private static final String TWITTER_TARGET = "http://twitter.com";
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer(REST_CONTAINER_TYPE,
getRestID(TWITTER_TARGET));
2. Если необходимо - зарегистрировать свою реализацию IRestResourceProcessor. Мы будем использовать XStreamResourceProcessor, который получает ответ от Twitter в XML и преобразует его в объект соответствующего класса. Регистрируется процессор через IRestClientContainerAdapter:
private IRestClientContainerAdapter getRestClientContainerAdapter()
{
return (IRestClientContainerAdapter) _container.getAdapter(IRestClientContainerAdapter.class);
}
//...
_adapter = getRestClientContainerAdapter();
_adapter.setResourceProcessor(new XStreamResourceProcessor());
{
return (IRestClientContainerAdapter) _container.getAdapter(IRestClientContainerAdapter.class);
}
//...
_adapter = getRestClientContainerAdapter();
_adapter.setResourceProcessor(new XStreamResourceProcessor());
Приведу код класса XStreamResourceProcessor:
package name.samolisov.ecf.remoteservices.rest.processor;
import java.util.Map;
import name.samolisov.ecf.remoteservices.rest.twitter.entity.Status;
import name.samolisov.ecf.remoteservices.rest.twitter.entity.User;
import org.eclipse.ecf.remoteservice.IRemoteCall;
import org.eclipse.ecf.remoteservice.rest.IRestCallable;
import org.eclipse.ecf.remoteservice.rest.RestException;
import org.eclipse.ecf.remoteservice.rest.resource.IRestResourceProcessor;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.Annotations;
public class XStreamResourceProcessor implements IRestResourceProcessor
{
private XStream _stream;
public XStreamResourceProcessor()
{
_stream = new XStream();
Annotations.configureAliases(_stream, User.class);
Annotations.configureAliases(_stream, Status.class);
}
@Override
@SuppressWarnings("unchecked")
public Object createResponseRepresentation(IRemoteCall call,
IRestCallable callable, Map responseHeaders, String responseBody)
throws RestException
{
return _stream.fromXML(responseBody);
}
}
import java.util.Map;
import name.samolisov.ecf.remoteservices.rest.twitter.entity.Status;
import name.samolisov.ecf.remoteservices.rest.twitter.entity.User;
import org.eclipse.ecf.remoteservice.IRemoteCall;
import org.eclipse.ecf.remoteservice.rest.IRestCallable;
import org.eclipse.ecf.remoteservice.rest.RestException;
import org.eclipse.ecf.remoteservice.rest.resource.IRestResourceProcessor;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.Annotations;
public class XStreamResourceProcessor implements IRestResourceProcessor
{
private XStream _stream;
public XStreamResourceProcessor()
{
_stream = new XStream();
Annotations.configureAliases(_stream, User.class);
Annotations.configureAliases(_stream, Status.class);
}
@Override
@SuppressWarnings("unchecked")
public Object createResponseRepresentation(IRemoteCall call,
IRestCallable callable, Map responseHeaders, String responseBody)
throws RestException
{
return _stream.fromXML(responseBody);
}
}
В конструкторе мы создаем объект класса XStream и настраиваем параметры сериализации/десериализации для классов User и Status с помощью аннотаций, которые будут использоваться в коде этих классов. Класс User инкапсулирует информацию о Twitter-аккаунте, а Status - о статусе пользователя, соответственно. Подробно про работу с XStream можно прочесть в статье Знакомимся: xstream - сериализуем Java-класс в XML.
3. Создаем экземпляр IRestCallable и регистрируем его с помощью IRestClientContainerAdapter:
IRestParameter [] parameters = RestParameterFactory.createParameters(COUNT, null);
IRestCallable callable = new RestCallable(TWITTER_RESOURCEPATH, TWITTER_RESOURCEPATH, parameters,
IRestCallable.RequestType.GET);
_adapter.registerCallable(callable, null);
IRestCallable callable = new RestCallable(TWITTER_RESOURCEPATH, TWITTER_RESOURCEPATH, parameters,
IRestCallable.RequestType.GET);
_adapter.registerCallable(callable, null);
4. Теперь данный экземпляр IRestCallable можно вызывать как обычный удаленный сервис с помощью Remote Services API. Напомню, что данный API поддерживает как синхронный, так и асинхронный вызов сервиса. Синхронный вызов осуществляется следующим образом:
private IRestCall getRestXMLCall()
{
return RestCallFactory.createRestCall(TWITTER_RESOURCEPATH);
}
//...
IRemoteService restClientService = _adapter.getRemoteService(_registration.getReference());
//..
try
{
System.out.println("sync calling...");
User result = (User) restClientService.callSync(getRestXMLCall());
System.out.println("sync called...");
System.out.println(result.getDescription());
System.out.println(result.getStatus().getText());
}
catch (ECFException e)
{
e.printStackTrace();
}
{
return RestCallFactory.createRestCall(TWITTER_RESOURCEPATH);
}
//...
IRemoteService restClientService = _adapter.getRemoteService(_registration.getReference());
//..
try
{
System.out.println("sync calling...");
User result = (User) restClientService.callSync(getRestXMLCall());
System.out.println("sync called...");
System.out.println(result.getDescription());
System.out.println(result.getStatus().getText());
}
catch (ECFException e)
{
e.printStackTrace();
}
Асинхронный вызов с колл-бэком может выглядеть так:
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!");
User result = (User) cce.getResponse();
System.out.println(result.getDescription());
System.out.println(result.getStatus().getText());
}
else
{
System.out.println("Remote call completed with exception: " + cce.getException());
}
}
}
};
}
//...
System.out.println("async calling...");
restClientService.callAsync(getRestXMLCall(), createRemoteCallListener());
System.out.println("async called...");
{
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!");
User result = (User) cce.getResponse();
System.out.println(result.getDescription());
System.out.println(result.getStatus().getText());
}
else
{
System.out.println("Remote call completed with exception: " + cce.getException());
}
}
}
};
}
//...
System.out.println("async calling...");
restClientService.callAsync(getRestXMLCall(), createRemoteCallListener());
System.out.println("async called...");
Следует учитывать, что значения параметров, указанных при регистрации IRestCallable можно рассматривать как значения по-умолчанию. При создании IRestCall при помощи RestCallFactory можно указывать свои значения этих параметров, которые будут использоваться при осуществлении конкретного вызова. Более того, можно указать даже таймаут, при превышении которого вызов будет считаться неудавшимся.
Более подробную информацию о Twitter REST API можно найти в соответствующем разделе справочной службы Twitter.
К сожалению, сейчас ECF содержит только клиентский REST-контейнер. Про разработку серверной части пока разговора не идет, однако, если у вас есть желание присоединиться к разработчикам Eclipse и вам нужна такая функциональность - милости просим.
Скачать примеры к статье (исходники и библиотеки. Zip, 410 Кб)
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter
2 комментария:
Спасибо интересный цикл статей.
Добавьте хоть контекстную рекламу к блогу (AdSense или Яндекс), я вам кликать буду :)
Наверное и не я один.
Хм, спасибо на добром слове. В яндекс меня не возьмут - мала пока посещаемость, а от Адсенса мне pin так и не дошел. Так что пока я наверное оставлю как есть, но в любом случае спасибо :)
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!