И снова здравствуйте! И снова много кода в качестве тьюториала. ;)
В среде OSGi понятие бандла является базовым. Как следует из его назначения, бандл, как компонент системы, с момента запуска способен жить собственной жизнью и реализовывать какие-либо сервисные функции.
В этом посте я хочу рассказать как бандлы могут общаться между собой.
Существует, например, простая задача, которая формулируется, так: «фоновый сервис в бандле workerbundle делает что-то называемое «preworkfunction» каждые 20 секунд, после того, как он это сделал, бандл handlerbundle должен узнать об этом и сообщить бандлу-исполнителю, чтобы тот запустил «workfunction».
Но начнем не с нее, а с более простой: есть некий бандл Registerer, который должен вести внутри себя реестр бандлов, которые могут в нем регистрироваться в момент запуска и удаляться из регистра в момент остановки.
Для решения этой задачи используем сервис EventAdmin, который представляет собой очередь обмена событиями между бандлами. Для начала напишем активатор бандла Registerer, который будет принимать сообщения от других бандлов.
Реализация приема сообщений
public class Activator implements BundleActivator {
private Register mRegister;
public void start(BundleContext context) throws Exception {
mRegister = new Register();
/*
* Зарегистрируем обработчик для подключений к основному бандлу.
*/
context.registerService(EventHandler.class.getName(),
new ActivationEventHandler(mRegister),
getHandlerServiceProperties(
"ru/futurelink/app/web/usecase/Activator"
));
/*
* Регистрируем и само приложение
*/
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("contextName", "app");
context.registerService(ApplicationConfiguration.class.getName(),
new ApplicationConfig(mRegister), props
);
}
public void stop(BundleContext context) throws Exception {}
protected Dictionary<String, Object> getHandlerServiceProperties(String... topics) {
Dictionary<String, Object> result = new Hashtable<String, Object>();
result.put(EventConstants.EVENT_TOPIC, topics);
return result;
}
}
Здесь мы регистрируем сервис, который обрабатывает сообщения с темой «ru/futurelink/app/web/usecase/Activator» (подписка на сообщения с такой темой). Стоит обратить внимание на то, что сообщения с такой темой может получать любой бандл, который на нее подписан. Так как сам по себе такой реестр не несет никакой пользы — его надо передать дальше, туда, где он будет использоваться, в сервис приложения RAP в этом случае. Поэтому мы переопределяем ApplicationConfig из этого поста, добавив параметр в конструктор.
public class ApplicationConfig implements ApplicationConfiguration {
private Register mRegister;
public ApplicationConfig(Register register) {
mRegister = register;
}
}
Сам по себе класс Register ничего особенного из себя не преставляет, реализация его не суть важна. Внутри него может быть просто коллекция имен зарегистрированных бандлов, например.
А вот это уже обработчик сообщений приемника реестра:
public class ActivationEventHandler implements EventHandler {
private Register mRegister;
public UseCaseActivationEventHandler(Register register) {
mRegister = register;
}
@Override
public void handleEvent(Event event) {
String bundle = (String) event.getProperty("bundleName");
Integer activated = (Integer) event.getProperty("activated");
BundleContext context = (BundleContext)event.getProperty("bundleContext");
if (activated == 1) {
mRegister.registerBundle(bundle, context);
} else {
mRegister.unregisterBundle(bundle);
}
}
}
На этом реализация обработчка и заканчивается. При получении сообщения с указанной выше темой будет регистрироваться бандл, название которого отправлено в параметре bundleName. Если параметр activated == 1, то считается, что бандл надо добавить в реестр, в противном случае — удалить оттуда.
Реализация отправки сообщений
Сделаем активатор бандла, который будет отправлять сообщения о своем запуске и остановке.
public class ClientActivator implements BundleActivator {
private ServiceTracker mServiceTracker;
private EventAdmin mEventAdmin;
private Logger mLogger;
private String mBundleName;
public ClientActivator() {}
public void addUsecase(UseCaseInfo info) {
mBundleName = info.getBundleName();
}
@Override
public void start(BundleContext context) throws Exception {
mServiceTracker = new ServiceTracker(
context, EventAdmin.class.getName(), null);
mServiceTracker.open();
mEventAdmin = (EventAdmin) mServiceTracker.getService();
postActivationEvent(context);
}
@Override
public void stop(BundleContext context) throws Exception {
postDeactivationEvent(context);
}
private void postActivationEvent(BundleContext context) {
if (mEventAdmin != null) {
// Отправляем сообщение об активации
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("bundleName", mBundleName);
props.put("bundleContext", context);
props.put("activated", 1);
mEventAdmin.postEvent(
new Event("ru/futurelink/app/web/usecase/Activator", props));
} else {
mLogger.error("Cannot get to EventAdmin service!");
}
}
private void postDeactivationEvent(BundleContext context) {
if (mEventAdmin != null) {
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("bundleName", mBundleName);
props.put("activated", 0);
mEventAdmin.postEvent(
new Event("ru/futurelink/app/web/usecase/Activator", props));
mServiceTracker.close();
} else {
System.out.println("Cannot get to EventAdmin service!");
}
}
protected Dictionary<String, Object> getHandlerServiceProperties(String... topics) {
Dictionary<String, Object> result = new Hashtable<String, Object>();
result.put(EventConstants.EVENT_TOPIC, topics);
return result;
}
}
Что тут происходит? При активации бандла мы получаем объект сервиса EventAdmin, который должен быть, естественно в системе запущен. Если он не запущен — mEventAdmin == null. Ну и далее по логике задачи — отправляем сообщение о старте и остановке нашего бандла.
Для чего это применяется у меня? После остановки бандла, я хочу, чтобы функциональность модуля, который в нем реализован была мгновенно отключена и недоступна. Никаких ошибок, эксепшнов — просто выключение куска сайта и уведомление пользователя о том, что эта часть в данный момент недоступна, когда он попытается ей воспользоваться. А также отключение любого связанного с этим бандлом функиоцнала. Основное приложение должно знать о состоянии его модулей, но при этом не быть от них зависимым, и в данном случае оно знает только то, что они ему скажут посредством события.
Кстати, нужно еще обратить внимание, что контекст бандла также передается в сообщении.
Реализация фонового сервиса, способного пообщаться с собратом и сообщить ему через EventAdmin о своем состоянии, учитывая изложенное выще, уже является тривиальной задачей.
Очень хочется вопросов и дополнений по теме… ;)
Автор: futurelink