/** * 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.internal.chart; import java.awt.image.BufferedImage; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Objects; import javax.imageio.ImageIO; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.ui.chart.ChartProvider; import org.eclipse.smarthome.ui.items.ItemUIRegistry; import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This servlet generates time-series charts for a given set of items. It * accepts the following HTTP parameters: * <ul> * <li>w: width in pixels of image to generate</li> * <li>h: height in pixels of image to generate</li> * <li>period: the time span for the x-axis. Value can be h,4h,8h,12h,D,3D,W,2W,M,2M,4M,Y</li> * <li>items: A comma separated list of item names to display</li> * <li>groups: A comma separated list of group names, whose members should be displayed</li> * <li>service: The persistence service name. If not supplied the first service found will be used.</li> * </ul> * * @author Chris Jackson * */ public class ChartServlet extends HttpServlet { private static final long serialVersionUID = 7700873790924746422L; private static final int CHART_HEIGHT = 240; private static final int CHART_WIDTH = 480; private static final String dateFormat = "yyyyMMddHHmm"; private static final DateFormat dateFormatter = new SimpleDateFormat(dateFormat); private final Logger logger = LoggerFactory.getLogger(ChartServlet.class); private String providerName = "default"; private int defaultHeight = CHART_HEIGHT; private int defaultWidth = CHART_WIDTH; private double scale = 1.0; // The URI of this servlet public static final String SERVLET_NAME = "/chart"; protected static final Map<String, Long> PERIODS = new HashMap<String, Long>(); static { PERIODS.put("h", 3600000L); PERIODS.put("4h", 14400000L); PERIODS.put("8h", 28800000L); PERIODS.put("12h", 43200000L); PERIODS.put("D", 86400000L); PERIODS.put("3D", 259200000L); PERIODS.put("W", 604800000L); PERIODS.put("2W", 1209600000L); PERIODS.put("M", 2592000000L); PERIODS.put("2M", 5184000000L); PERIODS.put("4M", 10368000000L); PERIODS.put("Y", 31536000000L); } protected HttpService httpService; protected ItemUIRegistry itemUIRegistry; static protected Map<String, ChartProvider> chartProviders = new HashMap<String, ChartProvider>(); public void setHttpService(HttpService httpService) { this.httpService = httpService; } public void unsetHttpService(HttpService httpService) { this.httpService = null; } public void setItemUIRegistry(ItemUIRegistry itemUIRegistry) { this.itemUIRegistry = itemUIRegistry; } public void unsetItemUIRegistry(ItemUIRegistry itemUIRegistry) { this.itemUIRegistry = null; } public void addChartProvider(ChartProvider provider) { chartProviders.put(provider.getName(), provider); } public void removeChartProvider(ChartProvider provider) { chartProviders.remove(provider.getName()); } static public Map<String, ChartProvider> getChartProviders() { return chartProviders; } protected void activate(Map<String, Object> config) { try { logger.debug("Starting up chart servlet at " + SERVLET_NAME); Hashtable<String, String> props = new Hashtable<String, String>(); httpService.registerServlet(SERVLET_NAME, this, props, createHttpContext()); } catch (NamespaceException e) { logger.error("Error during chart servlet startup", e); } catch (ServletException e) { logger.error("Error during chart servlet startup", e); } applyConfig(config); } protected void deactivate() { httpService.unregister(SERVLET_NAME); } protected void modified(Map<String, Object> config) { applyConfig(config); } /** * Handle the initial or a changed configuration. * * @param config the configuration */ private void applyConfig(Map<String, Object> config) { if (config == null) { return; } final String providerNameString = Objects.toString(config.get("provider"), null); if (providerNameString != null) { providerName = providerNameString; } final String defaultHeightString = Objects.toString(config.get("defaultHeight"), null); if (defaultHeightString != null) { defaultHeight = Integer.parseInt(defaultHeightString); } final String defaultWidthString = Objects.toString(config.get("defaultWidth"), null); if (defaultWidthString != null) { defaultWidth = Integer.parseInt(defaultWidthString); } final String scaleString = Objects.toString(config.get("scale"), null); if (scaleString != null) { scale = Double.parseDouble(scaleString); // Set scale to normal if the custom value is unrealistically low if (scale < 0.1) { scale = 1.0; } } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { logger.debug("Received incoming chart request: {}", req); int width = defaultWidth; try { width = Integer.parseInt(req.getParameter("w")); } catch (Exception e) { } int height = defaultHeight; try { String h = req.getParameter("h"); if (h != null) { Double d = Double.parseDouble(h) * scale; height = d.intValue(); } } catch (Exception e) { } // To avoid ambiguity you are not allowed to specify period, begin and end time at the same time. if (req.getParameter("period") != null && req.getParameter("begin") != null && req.getParameter("end") != null) { throw new ServletException("Do not specify the three parameters period, begin and end at the same time."); } // Read out the parameter period, begin and end and save them. Date timeBegin = null; Date timeEnd = null; Long period = PERIODS.get(req.getParameter("period")); if (period == null) { // use a day as the default period period = PERIODS.get("D"); } if (req.getParameter("begin") != null) { try { timeBegin = dateFormatter.parse(req.getParameter("begin")); } catch (ParseException e) { throw new ServletException("Begin and end must have this format: " + dateFormat + "."); } } if (req.getParameter("end") != null) { try { timeEnd = dateFormatter.parse(req.getParameter("end")); } catch (ParseException e) { throw new ServletException("Begin and end must have this format: " + dateFormat + "."); } } // Set begin and end time and check legality. if (timeBegin == null && timeEnd == null) { timeEnd = new Date(); timeBegin = new Date(timeEnd.getTime() - period); logger.debug("No begin or end is specified, use now as end and now-period as begin."); } else if (timeEnd == null) { timeEnd = new Date(timeBegin.getTime() + period); logger.debug("No end is specified, use begin + period as end."); } else if (timeBegin == null) { timeBegin = new Date(timeEnd.getTime() - period); logger.debug("No begin is specified, use end-period as begin"); } else if (timeEnd.before(timeBegin)) { throw new ServletException("The end is before the begin."); } // If a persistence service is specified, find the provider String serviceName = req.getParameter("service"); ChartProvider provider = getChartProviders().get(providerName); if (provider == null) { throw new ServletException("Could not get chart provider."); } // Set the content type to that provided by the chart provider res.setContentType("image/" + provider.getChartType()); try { BufferedImage chart = provider.createChart(serviceName, null, timeBegin, timeEnd, height, width, req.getParameter("items"), req.getParameter("groups")); ImageIO.write(chart, provider.getChartType().toString(), res.getOutputStream()); } catch (ItemNotFoundException e) { logger.debug(e.getMessage()); } catch (IllegalArgumentException e) { logger.warn("Illegal argument in chart: {}", e.getMessage()); } } /** * Creates a {@link HttpContext} * * @return a {@link HttpContext} */ protected HttpContext createHttpContext() { HttpContext defaultHttpContext = httpService.createDefaultHttpContext(); return defaultHttpContext; } /** * {@inheritDoc} */ @Override public void init(ServletConfig config) throws ServletException { } /** * {@inheritDoc} */ @Override public ServletConfig getServletConfig() { return null; } /** * {@inheritDoc} */ @Override public String getServletInfo() { return null; } /** * {@inheritDoc} */ @Override public void destroy() { } }