/**
* 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.ui.basic.internal.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.emf.common.util.EList;
import org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService;
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.basic.internal.WebAppConfig;
import org.eclipse.smarthome.ui.basic.internal.render.PageRenderer;
import org.eclipse.smarthome.ui.basic.render.RenderException;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the main servlet for the Basic UI.
* It serves the Html code based on the sitemap model.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Vlad Ivanov - BasicUI changes
*
*/
public class WebAppServlet extends BaseServlet {
private final Logger logger = LoggerFactory.getLogger(WebAppServlet.class);
/** the name of the servlet to be used in the URL */
public static final String SERVLET_NAME = "app";
private static final String CONTENT_TYPE_ASYNC = "application/xml;charset=UTF-8";
private static final String CONTENT_TYPE = "text/html;charset=UTF-8";
private PageRenderer renderer;
private SitemapSubscriptionService subscriptions;
private WebAppConfig config = new WebAppConfig();
protected Set<SitemapProvider> sitemapProviders = new CopyOnWriteArraySet<>();
public void setSitemapSubscriptionService(SitemapSubscriptionService subscriptions) {
this.subscriptions = subscriptions;
}
public void unsetSitemapSubscriptionService(SitemapSubscriptionService subscriptions) {
this.subscriptions = null;
}
public void addSitemapProvider(SitemapProvider sitemapProvider) {
this.sitemapProviders.add(sitemapProvider);
}
public void removeSitemapProvider(SitemapProvider sitemapProvider) {
this.sitemapProviders.remove(sitemapProvider);
}
public void setPageRenderer(PageRenderer renderer) {
renderer.setConfig(config);
this.renderer = renderer;
}
protected void activate(Map<String, Object> configProps) {
config.applyConfig(configProps);
try {
Hashtable<String, String> props = new Hashtable<String, String>();
httpService.registerServlet(WEBAPP_ALIAS + "/" + SERVLET_NAME, this, props, createHttpContext());
httpService.registerResources(WEBAPP_ALIAS, "web", null);
logger.info("Started Basic UI at " + WEBAPP_ALIAS + "/" + SERVLET_NAME);
} catch (NamespaceException e) {
logger.error("Error during servlet startup", e);
} catch (ServletException e) {
logger.error("Error during servlet startup", e);
}
}
protected void modified(Map<String, Object> configProps) {
config.applyConfig(configProps);
}
protected void deactivate() {
httpService.unregister(WEBAPP_ALIAS + "/" + SERVLET_NAME);
httpService.unregister(WEBAPP_ALIAS);
logger.info("Stopped Basic UI");
}
private void showSitemapList(ServletResponse res) throws IOException, RenderException {
PrintWriter resWriter;
resWriter = res.getWriter();
resWriter.append(renderer.renderSitemapList(sitemapProviders));
res.setContentType(CONTENT_TYPE);
}
/**
* {@inheritDoc}
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
logger.debug("Servlet request received!");
// read request parameters
String sitemapName = req.getParameter("sitemap");
String widgetId = req.getParameter("w");
String subscriptionId = req.getParameter("subscriptionId");
boolean async = "true".equalsIgnoreCase(req.getParameter("__async"));
if (sitemapName == null) {
sitemapName = config.getDefaultSitemap();
}
StringBuilder result = new StringBuilder();
Sitemap sitemap = null;
for (SitemapProvider sitemapProvider : sitemapProviders) {
sitemap = sitemapProvider.getSitemap(sitemapName);
if (sitemap != null) {
break;
}
}
try {
if (sitemap == null) {
showSitemapList(res);
return;
}
logger.debug("reading sitemap {}", sitemap.getName());
if (widgetId == null || widgetId.isEmpty() || widgetId.equals(sitemapName)) {
// we are at the homepage, so we render the children of the sitemap root node
if (subscriptionId != null) {
if (subscriptions.exists(subscriptionId)) {
subscriptions.setPageId(subscriptionId, sitemap.getName(), sitemapName);
} else {
logger.debug("Basic UI requested a non-existing event subscription id ({})", subscriptionId);
}
}
String label = sitemap.getLabel() != null ? sitemap.getLabel() : sitemapName;
result.append(renderer.processPage(sitemapName, sitemapName, label, sitemap.getChildren(), async));
} else if (!widgetId.equals("Colorpicker")) {
// we are on some subpage, so we have to render the children of the widget that has been selected
if (subscriptionId != null) {
if (subscriptions.exists(subscriptionId)) {
subscriptions.setPageId(subscriptionId, sitemap.getName(), widgetId);
} else {
logger.debug("Basic UI requested a non-existing event subscription id ({})", subscriptionId);
}
}
Widget w = renderer.getItemUIRegistry().getWidget(sitemap, widgetId);
if (w != null) {
String label = renderer.getItemUIRegistry().getLabel(w);
if (label == null) {
label = "undefined";
}
if (!(w instanceof LinkableWidget)) {
throw new RenderException("Widget '" + w + "' can not have any content");
}
EList<Widget> children = renderer.getItemUIRegistry().getChildren((LinkableWidget) w);
result.append(renderer.processPage(renderer.getItemUIRegistry().getWidgetId(w), sitemapName, label,
children, async));
}
}
} catch (RenderException e) {
throw new ServletException(e.getMessage(), e);
}
if (async) {
res.setContentType(CONTENT_TYPE_ASYNC);
} else {
res.setContentType(CONTENT_TYPE);
}
res.getWriter().append(result);
res.getWriter().close();
}
}