/**
* Copyright (c) 2014-2017 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.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.smarthome.core.auth.Role;
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.JSONResponse;
import org.eclipse.smarthome.io.rest.LocaleUtil;
import org.eclipse.smarthome.io.rest.SatisfiableRESTResource;
import org.eclipse.smarthome.io.rest.core.item.EnrichedItemDTOMapper;
import org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService;
import org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback;
import org.eclipse.smarthome.model.sitemap.Chart;
import org.eclipse.smarthome.model.sitemap.Frame;
import org.eclipse.smarthome.model.sitemap.Image;
import org.eclipse.smarthome.model.sitemap.LinkableWidget;
import org.eclipse.smarthome.model.sitemap.List;
import org.eclipse.smarthome.model.sitemap.Mapping;
import org.eclipse.smarthome.model.sitemap.Mapview;
import org.eclipse.smarthome.model.sitemap.Selection;
import org.eclipse.smarthome.model.sitemap.Setpoint;
import org.eclipse.smarthome.model.sitemap.Sitemap;
import org.eclipse.smarthome.model.sitemap.SitemapProvider;
import org.eclipse.smarthome.model.sitemap.Slider;
import org.eclipse.smarthome.model.sitemap.Switch;
import org.eclipse.smarthome.model.sitemap.Video;
import org.eclipse.smarthome.model.sitemap.Webview;
import org.eclipse.smarthome.model.sitemap.Widget;
import org.eclipse.smarthome.ui.items.ItemUIRegistry;
import org.glassfish.jersey.media.sse.EventOutput;
import org.glassfish.jersey.media.sse.OutboundEvent;
import org.glassfish.jersey.media.sse.SseBroadcaster;
import org.glassfish.jersey.media.sse.SseFeature;
import org.glassfish.jersey.server.BroadcasterListener;
import org.glassfish.jersey.server.ChunkedOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.MapMaker;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
/**
* <p>
* This class acts as a REST resource for sitemaps and provides different methods to interact with them, like retrieving
* a list of all available sitemaps or just getting the widgets of a single page.
* </p>
*
* @author Kai Kreuzer - Initial contribution and API
* @author Chris Jackson
* @author Yordan Zhelev - Added Swagger annotations
*/
@Path(SitemapResource.PATH_SITEMAPS)
@RolesAllowed({ Role.USER, Role.ADMIN })
@Api(value = SitemapResource.PATH_SITEMAPS)
public class SitemapResource
implements SatisfiableRESTResource, SitemapSubscriptionCallback, BroadcasterListener<OutboundEvent> {
private final Logger logger = LoggerFactory.getLogger(SitemapResource.class);
public static final String PATH_SITEMAPS = "sitemaps";
private static final String SEGMENT_EVENTS = "events";
private static final String X_ACCEL_BUFFERING_HEADER = "X-Accel-Buffering";
private static final long TIMEOUT_IN_MS = 30000;
private SseBroadcaster broadcaster;
@Context
UriInfo uriInfo;
@Context
private HttpServletResponse response;
private ItemUIRegistry itemUIRegistry;
private SitemapSubscriptionService subscriptions;
private java.util.List<SitemapProvider> sitemapProviders = new ArrayList<>();
private Map<String, EventOutput> eventOutputs = new MapMaker().weakValues().makeMap();
protected void activate() {
broadcaster = new SseBroadcaster();
broadcaster.add(this);
}
protected void deactivate() {
broadcaster.remove(this);
broadcaster = null;
}
public void setItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = itemUIRegistry;
}
public void unsetItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = null;
}
public void setSitemapSubscriptionService(SitemapSubscriptionService subscriptions) {
this.subscriptions = subscriptions;
}
public void unsetSitemapSubscriptionService(SitemapSubscriptionService subscriptions) {
this.subscriptions = null;
}
public void addSitemapProvider(SitemapProvider provider) {
sitemapProviders.add(provider);
}
public void removeSitemapProvider(SitemapProvider provider) {
sitemapProviders.remove(provider);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all available sitemaps.", response = SitemapDTO.class, responseContainer = "Collection")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getSitemaps() {
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
Object responseObject = getSitemapBeans(uriInfo.getAbsolutePathBuilder().build());
return Response.ok(responseObject).build();
}
@GET
@Path("/{sitemapname: [a-zA-Z_0-9]*}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get sitemap by name.", response = SitemapDTO.class)
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getSitemapData(@Context HttpHeaders headers,
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language,
@PathParam("sitemapname") @ApiParam(value = "sitemap name") String sitemapname,
@QueryParam("type") String type, @QueryParam("jsoncallback") @DefaultValue("callback") String callback) {
final Locale locale = LocaleUtil.getLocale(language);
logger.debug("Received HTTP GET request at '{}' for media type '{}'.",
new Object[] { uriInfo.getPath(), type });
Object responseObject = getSitemapBean(sitemapname, uriInfo.getBaseUriBuilder().build(), locale);
return Response.ok(responseObject).build();
}
@GET
@Path("/{sitemapname: [a-zA-Z_0-9]*}/{pageid: [a-zA-Z_0-9]*}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Polls the data for a sitemap.", response = PageDTO.class)
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
@ApiResponse(code = 404, message = "Sitemap with requested name does not exist or page does not exist, or page refers to a non-linkable widget"),
@ApiResponse(code = 400, message = "Invalid subscription id has been provided.") })
public Response getPageData(@Context HttpHeaders headers,
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language,
@PathParam("sitemapname") @ApiParam(value = "sitemap name") String sitemapname,
@PathParam("pageid") @ApiParam(value = "page id") String pageId,
@QueryParam("subscriptionid") @ApiParam(value = "subscriptionid", required = false) String subscriptionId) {
final Locale locale = LocaleUtil.getLocale(language);
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
if (subscriptionId != null) {
try {
subscriptions.setPageId(subscriptionId, sitemapname, pageId);
} catch (IllegalArgumentException e) {
return JSONResponse.createErrorResponse(Response.Status.BAD_REQUEST, e.getMessage());
}
}
if (headers.getRequestHeader("X-Atmosphere-Transport") != null) {
// Make the REST-API pseudo-compatible with openHAB 1.x
// The client asks Atmosphere for server push functionality,
// so we do a simply listening for changes on the appropriate items
blockUnlessChangeOccurs(sitemapname, pageId);
}
Object responseObject = getPageBean(sitemapname, pageId, uriInfo.getBaseUriBuilder().build(), locale);
return Response.ok(responseObject).build();
}
/**
* Creates a subscription for the stream of sitemap events.
*
* @return a subscription id
*/
@POST
@Path(SEGMENT_EVENTS + "/subscribe")
@ApiOperation(value = "Creates a sitemap event subscription.")
@ApiResponses(value = { @ApiResponse(code = 201, message = "Subscription created.") })
public Object createEventSubscription() {
String subscriptionId = subscriptions.createSubscription(this);
final EventOutput eventOutput = new SitemapEventOutput(subscriptions, subscriptionId);
broadcaster.add(eventOutput);
eventOutputs.put(subscriptionId, eventOutput);
URI uri = uriInfo.getBaseUriBuilder().path(PATH_SITEMAPS).path(SEGMENT_EVENTS).path(subscriptionId).build();
return Response.created(uri);
}
/**
* Subscribes the connecting client to the stream of sitemap events.
*
* @return {@link EventOutput} object associated with the incoming
* connection.
*/
@GET
@Path(SEGMENT_EVENTS + "/{subscriptionid: [a-zA-Z_0-9-]*}/")
@Produces(SseFeature.SERVER_SENT_EVENTS)
@ApiOperation(value = "Get sitemap events.", response = EventOutput.class)
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
@ApiResponse(code = 404, message = "Subscription not found.") })
public Object getSitemapEvents(
@PathParam("subscriptionid") @ApiParam(value = "subscription id") String subscriptionId,
@QueryParam("sitemap") @ApiParam(value = "sitemap name", required = false) String sitemapname,
@QueryParam("pageid") @ApiParam(value = "page id", required = false) String pageId) {
EventOutput eventOutput = eventOutputs.get(subscriptionId);
if (!subscriptions.exists(subscriptionId) || eventOutput == null) {
return JSONResponse.createResponse(Status.NOT_FOUND, null,
"Subscription id " + subscriptionId + " does not exist.");
}
if (sitemapname != null && pageId != null) {
subscriptions.setPageId(subscriptionId, sitemapname, pageId);
}
logger.debug("Client requested sitemap event stream for subscription {}.", subscriptionId);
// Disables proxy buffering when using an nginx http server proxy for this response.
// This allows you to not disable proxy buffering in nginx and still have working sse
response.addHeader(X_ACCEL_BUFFERING_HEADER, "no");
return eventOutput;
}
private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale locale) {
Sitemap sitemap = getSitemap(sitemapName);
if (sitemap != null) {
if (pageId.equals(sitemap.getName())) {
return createPageBean(sitemapName, sitemap.getLabel(), sitemap.getIcon(), sitemap.getName(),
sitemap.getChildren(), false, isLeaf(sitemap.getChildren()), uri, locale);
} else {
Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId);
if (pageWidget instanceof LinkableWidget) {
EList<Widget> children = itemUIRegistry.getChildren((LinkableWidget) pageWidget);
PageDTO pageBean = createPageBean(sitemapName, itemUIRegistry.getLabel(pageWidget),
itemUIRegistry.getCategory(pageWidget), pageId, children, false, isLeaf(children), uri,
locale);
EObject parentPage = pageWidget.eContainer();
while (parentPage instanceof Frame) {
parentPage = parentPage.eContainer();
}
if (parentPage instanceof Widget) {
String parentId = itemUIRegistry.getWidgetId((Widget) parentPage);
pageBean.parent = getPageBean(sitemapName, parentId, uri, locale);
pageBean.parent.widgets = null;
pageBean.parent.parent = null;
} else if (parentPage instanceof Sitemap) {
pageBean.parent = getPageBean(sitemapName, sitemap.getName(), uri, locale);
pageBean.parent.widgets = null;
}
return pageBean;
} else {
if (logger.isDebugEnabled()) {
if (pageWidget == null) {
logger.debug("Received HTTP GET request at '{}' for the unknown page id '{}'.", uri,
pageId);
} else {
logger.debug(
"Received HTTP GET request at '{}' for the page id '{}'. "
+ "This id refers to a non-linkable widget and is therefore no valid page id.",
uri, pageId);
}
}
throw new WebApplicationException(404);
}
}
} else {
logger.info("Received HTTP GET request at '{}' for the unknown sitemap '{}'.", uri, sitemapName);
throw new WebApplicationException(404);
}
}
public Collection<SitemapDTO> getSitemapBeans(URI uri) {
Collection<SitemapDTO> beans = new LinkedList<SitemapDTO>();
logger.debug("Received HTTP GET request at '{}'.", UriBuilder.fromUri(uri).build().toASCIIString());
for (SitemapProvider provider : sitemapProviders) {
for (String modelName : provider.getSitemapNames()) {
Sitemap sitemap = provider.getSitemap(modelName);
if (sitemap != null) {
SitemapDTO bean = new SitemapDTO();
bean.name = modelName;
bean.icon = sitemap.getIcon();
bean.label = sitemap.getLabel();
bean.link = UriBuilder.fromUri(uri).path(bean.name).build().toASCIIString();
bean.homepage = new PageDTO();
bean.homepage.link = bean.link + "/" + sitemap.getName();
beans.add(bean);
}
}
}
return beans;
}
public SitemapDTO getSitemapBean(String sitemapname, URI uri, Locale locale) {
Sitemap sitemap = getSitemap(sitemapname);
if (sitemap != null) {
return createSitemapBean(sitemapname, sitemap, uri, locale);
} else {
logger.info("Received HTTP GET request at '{}' for the unknown sitemap '{}'.", uriInfo.getPath(),
sitemapname);
throw new WebApplicationException(404);
}
}
private SitemapDTO createSitemapBean(String sitemapName, Sitemap sitemap, URI uri, Locale locale) {
SitemapDTO bean = new SitemapDTO();
bean.name = sitemapName;
bean.icon = sitemap.getIcon();
bean.label = sitemap.getLabel();
bean.link = UriBuilder.fromUri(uri).path(SitemapResource.PATH_SITEMAPS).path(bean.name).build().toASCIIString();
bean.homepage = createPageBean(sitemap.getName(), sitemap.getLabel(), sitemap.getIcon(), sitemap.getName(),
sitemap.getChildren(), true, false, uri, locale);
return bean;
}
private PageDTO createPageBean(String sitemapName, String title, String icon, String pageId, EList<Widget> children,
boolean drillDown, boolean isLeaf, URI uri, Locale locale) {
PageDTO bean = new PageDTO();
bean.id = pageId;
bean.title = title;
bean.icon = icon;
bean.leaf = isLeaf;
bean.link = UriBuilder.fromUri(uri).path(PATH_SITEMAPS).path(sitemapName).path(pageId).build().toASCIIString();
if (children != null) {
int cntWidget = 0;
for (Widget widget : children) {
String widgetId = pageId + "_" + cntWidget;
WidgetDTO subWidget = createWidgetBean(sitemapName, widget, drillDown, uri, widgetId, locale);
if (subWidget != null) {
bean.widgets.add(subWidget);
}
cntWidget++;
}
} else {
bean.widgets = null;
}
return bean;
}
private WidgetDTO createWidgetBean(String sitemapName, Widget widget, boolean drillDown, URI uri, String widgetId,
Locale locale) {
// Test visibility
if (itemUIRegistry.getVisiblity(widget) == false) {
return null;
}
WidgetDTO bean = new WidgetDTO();
if (widget.getItem() != null) {
try {
Item item = itemUIRegistry.getItem(widget.getItem());
if (item != null) {
bean.item = EnrichedItemDTOMapper.map(item, false, UriBuilder.fromUri(uri).build(), locale);
}
} catch (ItemNotFoundException e) {
logger.debug(e.getMessage());
}
}
bean.widgetId = widgetId;
bean.icon = itemUIRegistry.getCategory(widget);
bean.labelcolor = itemUIRegistry.getLabelColor(widget);
bean.valuecolor = itemUIRegistry.getValueColor(widget);
bean.label = itemUIRegistry.getLabel(widget);
bean.type = widget.eClass().getName();
if (widget instanceof LinkableWidget) {
LinkableWidget linkableWidget = (LinkableWidget) widget;
EList<Widget> children = itemUIRegistry.getChildren(linkableWidget);
if (widget instanceof Frame) {
int cntWidget = 0;
for (Widget child : children) {
widgetId += "_" + cntWidget;
WidgetDTO subWidget = createWidgetBean(sitemapName, child, drillDown, uri, widgetId, locale);
if (subWidget != null) {
bean.widgets.add(subWidget);
cntWidget++;
}
}
} else if (children.size() > 0) {
String pageName = itemUIRegistry.getWidgetId(linkableWidget);
bean.linkedPage = createPageBean(sitemapName, itemUIRegistry.getLabel(widget),
itemUIRegistry.getCategory(widget), pageName, drillDown ? children : null, drillDown,
isLeaf(children), uri, locale);
}
}
if (widget instanceof Switch) {
Switch switchWidget = (Switch) widget;
for (Mapping mapping : switchWidget.getMappings()) {
MappingDTO mappingBean = new MappingDTO();
mappingBean.command = mapping.getCmd();
mappingBean.label = mapping.getLabel();
bean.mappings.add(mappingBean);
}
}
if (widget instanceof Selection) {
Selection selectionWidget = (Selection) widget;
for (Mapping mapping : selectionWidget.getMappings()) {
MappingDTO mappingBean = new MappingDTO();
mappingBean.command = mapping.getCmd();
mappingBean.label = mapping.getLabel();
bean.mappings.add(mappingBean);
}
}
if (widget instanceof Slider) {
Slider sliderWidget = (Slider) widget;
bean.sendFrequency = sliderWidget.getFrequency();
bean.switchSupport = sliderWidget.isSwitchEnabled();
}
if (widget instanceof List) {
List listWidget = (List) widget;
bean.separator = listWidget.getSeparator();
}
if (widget instanceof Image) {
Image imageWidget = (Image) widget;
String wId = itemUIRegistry.getWidgetId(widget);
if (uri.getPort() < 0 || uri.getPort() == 80) {
bean.url = uri.getScheme() + "://" + uri.getHost() + "/proxy?sitemap=" + sitemapName
+ ".sitemap&widgetId=" + wId;
} else {
bean.url = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort() + "/proxy?sitemap="
+ sitemapName + ".sitemap&widgetId=" + wId;
}
if (imageWidget.getRefresh() > 0) {
bean.refresh = imageWidget.getRefresh();
}
}
if (widget instanceof Video) {
Video videoWidget = (Video) widget;
String wId = itemUIRegistry.getWidgetId(widget);
if (videoWidget.getEncoding() != null) {
bean.encoding = videoWidget.getEncoding();
}
if (uri.getPort() < 0 || uri.getPort() == 80) {
bean.url = uri.getScheme() + "://" + uri.getHost() + "/proxy?sitemap=" + sitemapName
+ ".sitemap&widgetId=" + wId;
} else {
bean.url = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort() + "/proxy?sitemap="
+ sitemapName + ".sitemap&widgetId=" + wId;
}
}
if (widget instanceof Webview) {
Webview webViewWidget = (Webview) widget;
bean.url = webViewWidget.getUrl();
bean.height = webViewWidget.getHeight();
}
if (widget instanceof Mapview) {
Mapview mapViewWidget = (Mapview) widget;
bean.height = mapViewWidget.getHeight();
}
if (widget instanceof Chart) {
Chart chartWidget = (Chart) widget;
bean.service = chartWidget.getService();
bean.period = chartWidget.getPeriod();
if (chartWidget.getRefresh() > 0) {
bean.refresh = chartWidget.getRefresh();
}
}
if (widget instanceof Setpoint) {
Setpoint setpointWidget = (Setpoint) widget;
bean.minValue = setpointWidget.getMinValue();
bean.maxValue = setpointWidget.getMaxValue();
bean.step = setpointWidget.getStep();
}
return bean;
}
private boolean isLeaf(EList<Widget> children) {
for (Widget w : children) {
if (w instanceof Frame) {
if (isLeaf(((Frame) w).getChildren())) {
return false;
}
} else if (w instanceof LinkableWidget) {
LinkableWidget linkableWidget = (LinkableWidget) w;
if (itemUIRegistry.getChildren(linkableWidget).size() > 0) {
return false;
}
}
}
return true;
}
private Sitemap getSitemap(String sitemapname) {
for (SitemapProvider provider : sitemapProviders) {
Sitemap sitemap = provider.getSitemap(sitemapname);
if (sitemap != null) {
return sitemap;
}
}
return null;
}
private void blockUnlessChangeOccurs(String sitemapname, String pageId) {
Sitemap sitemap = getSitemap(sitemapname);
if (sitemap != null) {
if (pageId.equals(sitemap.getName())) {
waitForChanges(sitemap.getChildren());
} else {
Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId);
if (pageWidget instanceof LinkableWidget) {
EList<Widget> children = itemUIRegistry.getChildren((LinkableWidget) pageWidget);
waitForChanges(children);
}
}
}
}
/**
* This method only returns when a change has occurred to any item on the
* page to display or if the timeout is reached
*
* @param widgets
* the widgets of the page to observe
*/
private boolean waitForChanges(EList<Widget> widgets) {
long startTime = (new Date()).getTime();
boolean timeout = false;
BlockingStateChangeListener listener = new BlockingStateChangeListener();
// let's get all items for these widgets
Set<GenericItem> items = getAllItems(widgets);
for (GenericItem item : items) {
item.addStateChangeListener(listener);
}
while (!listener.hasChangeOccurred() && !timeout) {
timeout = (new Date()).getTime() - startTime > TIMEOUT_IN_MS;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
timeout = true;
break;
}
}
for (GenericItem item : items) {
item.removeStateChangeListener(listener);
}
return !timeout;
}
/**
* 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;
}
/**
* This is a state change listener, which is merely used to determine, if a
* state change has occurred on one of a list of items.
*
* @author Kai Kreuzer - Initial contribution and API
*
*/
private static class BlockingStateChangeListener implements StateChangeListener {
private boolean changed = false;
/**
* {@inheritDoc}
*/
@Override
public void stateChanged(Item item, State oldState, State newState) {
changed = true;
}
/**
* determines, whether a state change has occurred since its creation
*
* @return true, if a state has changed
*/
public boolean hasChangeOccurred() {
return changed;
}
/**
* {@inheritDoc}
*/
@Override
public void stateUpdated(Item item, State state) {
// ignore if the state did not change
}
}
@Override
public void onEvent(SitemapEvent event) {
OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
OutboundEvent outboundEvent = eventBuilder.name("event").mediaType(MediaType.APPLICATION_JSON_TYPE).data(event)
.build();
broadcaster.broadcast(outboundEvent);
}
@Override
public void onClose(ChunkedOutput<OutboundEvent> event) {
if (event instanceof SitemapEventOutput) {
SitemapEventOutput sitemapEvent = (SitemapEventOutput) event;
logger.debug("SSE connection for subscription {} has been closed.", sitemapEvent.getSubscriptionId());
subscriptions.removeSubscription(sitemapEvent.getSubscriptionId());
}
}
@Override
public void onException(ChunkedOutput<OutboundEvent> event, Exception e) {
// the exception is usually "null" and onClose() is automatically called afterwards
// - so let's don't do anything in this method.
}
@Override
public boolean isSatisfied() {
return itemUIRegistry != null && subscriptions != null;
}
}