Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
added sitemap event subscription mechanism
Browse files Browse the repository at this point in the history
Signed-off-by: Kai Kreuzer <kai@openhab.org>
  • Loading branch information
kaikreuzer committed Aug 22, 2016
1 parent 81a0fdf commit 610cac5
Show file tree
Hide file tree
Showing 12 changed files with 531 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ Import-Package: io.swagger.annotations;resolution:=optional,
org.eclipse.smarthome.model.core,
org.eclipse.smarthome.model.sitemap,
org.eclipse.smarthome.ui.items,
org.glassfish.jersey.media.sse,
org.glassfish.jersey.server,
org.slf4j
Service-Component: OSGI-INF/sitemaprest.xml
Service-Component: OSGI-INF/*.xml
Export-Package: org.eclipse.smarthome.io.rest.sitemap
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipse.smarthome.io.rest.sitemap">
<implementation class="org.eclipse.smarthome.io.rest.sitemap.internal.SitemapResource"/>
<reference bind="setItemUIRegistry" cardinality="1..1" interface="org.eclipse.smarthome.ui.items.ItemUIRegistry" name="ItemUIRegistry" policy="dynamic" unbind="unsetItemUIRegistry"/>
<reference bind="setSitemapSubscriptionService" cardinality="1..1" interface="org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService" name="SitemapSubscriptions" policy="static" unbind="unsetSitemapSubscriptionService"/>
<reference bind="addSitemapProvider" cardinality="0..n" interface="org.eclipse.smarthome.model.sitemap.SitemapProvider" name="SitemapProvider" policy="dynamic" unbind="removeSitemapProvider"/>
<service>
<provide interface="org.eclipse.smarthome.io.rest.sitemap.internal.SitemapResource"/>
<provide interface="org.eclipse.smarthome.io.rest.RESTResource"/>
</service>
<reference bind="addSitemapProvider" cardinality="0..n" interface="org.eclipse.smarthome.model.sitemap.SitemapProvider" name="SitemapProvider" policy="dynamic" unbind="removeSitemapProvider"/>
</scr:component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipse.smarthome.io.rest.sitemapsubscription">
<implementation class="org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService"/>
<service>
<provide interface="org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService"/>
</service>
</scr:component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.io.rest.sitemap;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.emf.common.util.EList;
import org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener;
import org.eclipse.smarthome.io.rest.sitemap.internal.SitemapEvent;
import org.eclipse.smarthome.model.sitemap.LinkableWidget;
import org.eclipse.smarthome.model.sitemap.Sitemap;
import org.eclipse.smarthome.model.sitemap.SitemapProvider;
import org.eclipse.smarthome.model.sitemap.Widget;
import org.eclipse.smarthome.ui.items.ItemUIRegistry;

/**
* This is a service that provides the possibility to manage subscriptions to sitemaps.
* As such subscriptions are stateful, they need to be created and removed upon disposal.
* The subscription mechanism makes sure that only events for widgets of the currently active sitemap page are sent as
* events to the subscriber.
* For this to work correctly, the subscriber needs to make sure that setPageId is called whenever it switches to a new
* page.
*
* @author Kai Kreuzer - Initial contribution and API
*/
public class SitemapSubscriptionService {

public interface SitemapSubscriptionCallback {

void onEvent(SitemapEvent event);
}

private final Map<String, String> pageOfSubscriptionMap = new ConcurrentHashMap<>();
private ItemUIRegistry itemUIRegistry;
private Map<String, SitemapSubscriptionCallback> callbacks = new ConcurrentHashMap<>();
private Map<String, PageChangeListener> pageChangeListeners = new ConcurrentHashMap<>();
private List<SitemapProvider> sitemapProviders = new ArrayList<>();

public SitemapSubscriptionService() {
}

protected void activate() {
}

protected void deactivate() {
pageOfSubscriptionMap.clear();
callbacks.clear();
for (PageChangeListener listener : pageChangeListeners.values()) {
listener.dispose();
}
pageChangeListeners.clear();
}

protected void setItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = itemUIRegistry;
}

protected void unsetItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = null;
}

protected void addSitemapProvider(SitemapProvider provider) {
sitemapProviders.add(provider);
}

protected void removeSitemapProvider(SitemapProvider provider) {
sitemapProviders.remove(provider);
}

/**
* Creates a new subscription with the given id.
*
* @param callback an instance that should receive the events
* @returns a unique id that identifies the subscription
*/
public String createSubscription(SitemapSubscriptionCallback callback) {
String subscriptionId = UUID.randomUUID().toString();
callbacks.put(subscriptionId, callback);
return subscriptionId;
}

/**
* Removes an existing subscription
*
* @param subscriptionId the id of the subscription to remove
*/
public void removeSubscription(String subscriptionId) {
pageOfSubscriptionMap.remove(subscriptionId);
callbacks.remove(subscriptionId);
PageChangeListener listener = pageChangeListeners.remove(subscriptionId);
if (listener != null) {
listener.dispose();
}
}

/**
* Checks whether a subscription with a given id (still) exists.
*
* @param subscriptionId the id of the subscription to check
* @return true, if it exists, false otherwise
*/
public boolean exists(String subscriptionId) {
return callbacks.containsKey(subscriptionId);
}

/**
* Retrieves the current page id for a subscription.
*
* @param subscriptionId the subscription to get the page id for
* @return the id of the currently active page
*/
public String getPageId(String subscriptionId) {
return pageOfSubscriptionMap.get(subscriptionId).split("-")[1];
}

/**
* Retrieves the current sitemap name for a subscription.
*
* @param subscriptionId the subscription to get the sitemap name for
* @return the name of the current sitemap
*/
public String getSitemapName(String subscriptionId) {
return pageOfSubscriptionMap.get(subscriptionId).split("-")[0];
}

/**
* Updates the subscription to send events for the provided page id.
*
* @param subscriptionId the subscription to update
* @param sitemapName the current sitemap name
* @param pageId the current page id
*/
public void setPageId(String subscriptionId, String sitemapName, String pageId) {
if (exists(subscriptionId)) {
pageOfSubscriptionMap.put(subscriptionId, getValue(sitemapName, pageId));
removeOldListener(subscriptionId);
initNewListener(subscriptionId, sitemapName, pageId);
} else {
throw new IllegalArgumentException("Subscription " + subscriptionId + " does not exist!");
}
}

private void initNewListener(String subscriptionId, String sitemapName, String pageId) {
EList<Widget> widgets = null;
Sitemap sitemap = getSitemap(sitemapName);
if (sitemap != null) {
if (pageId.equals(sitemap.getName())) {
widgets = sitemap.getChildren();
} else {
Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId);
if (pageWidget instanceof LinkableWidget) {
widgets = itemUIRegistry.getChildren((LinkableWidget) pageWidget);
}
}
}
if (widgets != null) {
PageChangeListener listener = new PageChangeListener(sitemapName, pageId, itemUIRegistry, widgets,
callbacks.get(subscriptionId));
pageChangeListeners.put(subscriptionId, listener);
}
}

private void removeOldListener(String subscriptionId) {
PageChangeListener oldListener = pageChangeListeners.get(subscriptionId);
if (oldListener != null) {
oldListener.dispose();
}
}

private String getValue(String sitemapName, String pageId) {
return sitemapName + "-" + pageId;
}

private Sitemap getSitemap(String sitemapName) {
for (SitemapProvider provider : sitemapProviders) {
Sitemap sitemap = provider.getSitemap(sitemapName);
if (sitemap != null) {
return sitemap;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.io.rest.sitemap.internal;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.emf.common.util.EList;
import org.eclipse.smarthome.core.items.GenericItem;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.items.ItemNotFoundException;
import org.eclipse.smarthome.core.items.StateChangeListener;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback;
import org.eclipse.smarthome.model.sitemap.Frame;
import org.eclipse.smarthome.model.sitemap.Widget;
import org.eclipse.smarthome.ui.items.ItemUIRegistry;

/**
* This is a class that listens on item state change events and creates sitemap events for a dedicated sitemap page.
*
* @author Kai Kreuzer - Initial contribution and API
*
*/
public class PageChangeListener implements StateChangeListener {

private final String sitemapName;
private final String pageId;
private final ItemUIRegistry itemUIRegistry;
private final EList<Widget> widgets;
private final Set<GenericItem> items;
private final SitemapSubscriptionCallback callback;

/**
* Creates a new instance.
*
* @param sitemapName the sitemap name of the page
* @param pageId the id of the page for which events are created
* @param itemUIRegistry the ItemUIRegistry which is needed for the functionality
* @param widgets the list of widgets that are part of the page.
* @param callback the instance that should receive the created sitemap events
*/
public PageChangeListener(String sitemapName, String pageId, ItemUIRegistry itemUIRegistry, EList<Widget> widgets,
SitemapSubscriptionCallback callback) {
this.sitemapName = sitemapName;
this.pageId = pageId;
this.itemUIRegistry = itemUIRegistry;
this.widgets = widgets;
this.callback = callback;
items = getAllItems(widgets);
for (GenericItem item : items) {
item.addStateChangeListener(this);
}
}

/**
* Disposes this instance and releases all resources.
*/
public void dispose() {
for (GenericItem item : items) {
item.removeStateChangeListener(this);
}
}

/**
* Collects all items that are represented by a given list of widgets
*
* @param widgets
* the widget list to get the items for added to all bundles containing REST resources
* @return all items that are represented by the list of widgets
*/
private Set<GenericItem> getAllItems(EList<Widget> widgets) {
Set<GenericItem> items = new HashSet<GenericItem>();
if (itemUIRegistry != null) {
for (Widget widget : widgets) {
String itemName = widget.getItem();
if (itemName != null) {
try {
Item item = itemUIRegistry.getItem(itemName);
if (item instanceof GenericItem) {
final GenericItem gItem = (GenericItem) item;
items.add(gItem);
}
} catch (ItemNotFoundException e) {
// ignore
}
} else {
if (widget instanceof Frame) {
items.addAll(getAllItems(((Frame) widget).getChildren()));
}
}
}
}
return items;
}

@Override
public void stateChanged(Item item, State oldState, State newState) {
Set<SitemapEvent> events = constructSitemapEvents(item, oldState, newState);
for (SitemapEvent event : events) {
callback.onEvent(event);
}
}

@Override
public void stateUpdated(Item item, State state) {
}

private Set<SitemapEvent> constructSitemapEvents(Item item, State oldState, State newState) {
Set<SitemapEvent> events = new HashSet<>();
for (Widget w : widgets) {
if (w.getItem().equals(item.getName())) {
SitemapWidgetEvent event = new SitemapWidgetEvent();
event.sitemapName = sitemapName;
event.pageId = pageId;
event.label = itemUIRegistry.getLabel(w);
event.labelcolor = itemUIRegistry.getLabelColor(w);
event.category = itemUIRegistry.getCategory(w);
event.state = newState.toString();
event.valuecolor = itemUIRegistry.getValueColor(w);
event.widgetId = itemUIRegistry.getWidgetId(w);
event.visibility = itemUIRegistry.getVisiblity(w);
events.add(event);
}
}
return events;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.io.rest.sitemap.internal;

/**
* A general sitemap event, meant to be sub-classed.
*
* @author Kai Kreuzer - Initial contribution and API
*/
public class SitemapEvent {

/** The sitemap name this event is for */
public String sitemapName;

/** The page id this event is for */
public String pageId;

public SitemapEvent() {
}
}
Loading

0 comments on commit 610cac5

Please sign in to comment.