Introduced new ui.start bundle, which brings custom lifecycle status … (#419)

* Introduced new ui.start bundle, which brings custom lifecycle status HTTP pages as well as an 404 error page.

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer 2018-10-31 09:16:10 +01:00 committed by GitHub
parent 4cb79e4fe2
commit 39f0e17002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 357 additions and 11 deletions

View File

@ -1,6 +1,5 @@
Manifest-Version: 1.0
Automatic-Module-Name: org.openhab.ui.dashboard
Bundle-ActivationPolicy: lazy
Bundle-ManifestVersion: 2
Bundle-Name: openHAB Dashboard UI
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@ -19,7 +18,6 @@ Import-Package:
org.osgi.framework,
org.osgi.service.cm,
org.osgi.service.component,
org.osgi.service.component.annotations;resolution:=optional,
org.osgi.service.http,
org.slf4j
Service-Component: OSGI-INF/*.xml

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-2018 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.openhab.ui.dashboard;
/**
* This is a marker interface to declare that the dashboard is up and ready to be used.
*
* @author Kai Kreuzer - Initial contribution
*
*/
public interface DashboardReady {
}

View File

@ -24,10 +24,12 @@ import org.eclipse.smarthome.core.i18n.LocaleProvider;
import org.eclipse.smarthome.core.i18n.TranslationProvider;
import org.eclipse.smarthome.core.net.HttpServiceUtil;
import org.eclipse.smarthome.core.net.NetworkAddressService;
import org.openhab.ui.dashboard.DashboardReady;
import org.openhab.ui.dashboard.DashboardTile;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.ComponentException;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
@ -46,8 +48,8 @@ import org.slf4j.LoggerFactory;
* @author Laurent Garnier - internationalization
* @author Hilbrand Bouwkamp - internationalization
*/
@Component(service = DashboardService.class, immediate = true, name = "org.openhab.dashboard")
public class DashboardService {
@Component(service = { DashboardService.class, DashboardReady.class }, immediate = true, name = "org.openhab.dashboard")
public class DashboardService implements DashboardReady {
public static final String DASHBOARD_ALIAS = "/start";
@ -166,10 +168,10 @@ public class DashboardService {
try {
indexTemplate = IOUtils.toString(index.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
throw new ComponentException(e);
}
} else {
throw new RuntimeException("Cannot find index.html - failed to initialize Dashboard servlet");
throw new ComponentException("Cannot find index.html - failed to initialize Dashboard servlet");
}
URL entry = bundleContext.getBundle().getEntry("templates/entry.html");
@ -177,10 +179,10 @@ public class DashboardService {
try {
entryTemplate = IOUtils.toString(entry.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
throw new ComponentException(e);
}
} else {
throw new RuntimeException("Cannot find entry.html - failed to initialize Dashboard servlet");
throw new ComponentException("Cannot find entry.html - failed to initialize Dashboard servlet");
}
URL warn = bundleContext.getBundle().getEntry("templates/warn.html");
@ -188,7 +190,7 @@ public class DashboardService {
try {
warnTemplate = IOUtils.toString(warn.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
throw new ComponentException(e);
}
} else {
throw new RuntimeException("Cannot find warn.html - failed to initialize Dashboard servlet");
@ -199,10 +201,10 @@ public class DashboardService {
try {
setupTemplate = IOUtils.toString(setup.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
throw new ComponentException(e);
}
} else {
throw new RuntimeException("Cannot find setup.html - failed to initialize Dashboard servlet");
throw new ComponentException("Cannot find setup.html - failed to initialize Dashboard servlet");
}
return new DashboardServlet(configurationAdmin, indexTemplate, entryTemplate, warnTemplate, setupTemplate,

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.ui.start</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,21 @@
Manifest-Version: 1.0
Automatic-Module-Name: org.openhab.ui.start
Bundle-ManifestVersion: 2
Bundle-Name: openHAB Start UI
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-SymbolicName: org.openhab.ui.start
Bundle-Vendor: openHAB
Bundle-Version: 2.4.0.qualifier
Import-Package:
javax.servlet,
javax.servlet.http,
org.apache.commons.io,
org.eclipse.jdt.annotation;resolution:=optional,
org.openhab.ui.dashboard,
org.osgi.framework,
org.osgi.service.cm,
org.osgi.service.component,
org.osgi.service.http,
org.slf4j
Service-Component: OSGI-INF/*.xml
Bundle-ActivationPolicy: lazy

View File

@ -0,0 +1 @@
/*.xml

View File

@ -0,0 +1,37 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>About</title>
</head>
<body lang="EN-US">
<h2>About This Content</h2>
<p>March 22, 2017</p>
<h3>License</h3>
<p>The openHAB community makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise
indicated below, the Content is provided to you under the terms and conditions of the
Eclipse Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is available
at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
<p>If you did not receive this Content directly from the openHAB community, the Content is
being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
apply to your use of any object code in the Content. Check the Redistributor's license that was
provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
indicated below, the terms and conditions of the EPL still apply to any source code in the Content
and such source code may be obtained at <a href="http://www.openhab.org/">openhab.org</a>.</p>
<h3>Third Party Content</h3>
<p>The Content includes items that have been sourced from third parties as set out below. If you
did not receive this Content directly from the openHAB project, the following is provided
for informational purposes only, and you should look to the Redistributor's license for
terms and conditions of use.</p>
<p><em>
<strong>Simple HttpErrorPages</strong> <br/><br/>
<a href="https://github.com/AndiDittrich/HttpErrorPages">HttpErrorPages</a> obtained from Github repository under MIT License.<br/>
</em></p>
</body>
</html>

View File

@ -0,0 +1,7 @@
output.. = target/classes/
bin.includes = META-INF/,\
.,\
OSGI-INF/,\
about.html,\
pages/
source.. = src/main/java/

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>We've got some trouble | 404 - Resource not found</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#F60;}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,.8);box-shadow:inset 0 0 100px rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#eee}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style>
</head>
<body>
<div class="cover"><h1>Resource not found <small>| Error 404</small></h1><p class="lead">The requested resource could not be found.</p></div>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="refresh" content="5">
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${message}</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#F60;}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,.8);box-shadow:inset 0 0 100px rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#eee}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style>
</head>
<body>
<div class="cover"><h1>${message}</h1><p class="lead">${submessage}</p></div>
</body>
</html>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.core</groupId>
<artifactId>pom-bundles</artifactId>
<version>2.4.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.ui.start</artifactId>
<packaging>eclipse-plugin</packaging>
<name>openHAB Start UI</name>
</project>

View File

@ -0,0 +1,173 @@
/**
* Copyright (c) 2015-2018 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.openhab.ui.start.internal;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.openhab.ui.dashboard.DashboardReady;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.ComponentException;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This servlet registers status (starting/stopping/updating) pages and serves the 404 page if system is started and an
* unknown url is called.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@Component(immediate = true)
public class RootServlet extends HttpServlet {
private static final long serialVersionUID = -2091860295954594917L;
private final Logger logger = LoggerFactory.getLogger(RootServlet.class);
protected HttpService httpService;
// an enumeration for the state the whole system is in
private enum LifeCycleState {
STARTING,
STARTED,
STOPPING,
UPDATING;
}
private String page404;
private String pageStatus;
private DashboardReady dashboardStarted;
private LifeCycleState lifecycleState = LifeCycleState.STARTING;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (dashboardStarted != null) {
// all is up and running
if (req.getRequestURI().equals("/")) {
resp.sendRedirect("/start/index");
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().append(page404);
resp.getWriter().close();
}
} else {
// report current system state
String message = null;
String subMessage = null;
switch (lifecycleState) {
case STARTING:
message = "openHAB is starting...";
subMessage = "Please wait a moment!";
break;
case UPDATING:
message = "openHAB is updating...";
subMessage = "Please wait a moment!";
break;
case STOPPING:
message = "openHAB is shutting down...";
subMessage = "Please stand by.";
break;
default:
throw new IllegalStateException("Invalid system state " + lifecycleState);
}
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().append(pageStatus.replace("${message}", message).replace("${submessage}", subMessage));
resp.getWriter().close();
}
}
@Activate
protected void activate(ComponentContext context) {
try {
httpService.registerServlet("/", this, new Properties(), httpService.createDefaultHttpContext());
} catch (ServletException | NamespaceException e) {
logger.error("Failed registering root servlet!", e);
}
URL notfound = context.getBundleContext().getBundle().getEntry("pages/404.html");
if (notfound != null) {
try {
page404 = IOUtils.toString(notfound.openStream());
} catch (IOException e) {
throw new ComponentException(e);
}
} else {
throw new ComponentException("Cannot find 404.html - failed to initialize root servlet");
}
URL status = context.getBundleContext().getBundle().getEntry("pages/status.html");
if (status != null) {
try {
pageStatus = IOUtils.toString(status.openStream());
} catch (IOException e) {
throw new ComponentException(e);
}
} else {
throw new ComponentException("Cannot find status.html - failed to initialize root servlet");
}
// we can determine whether the whole framework is shutdown by listening to a STOPPING event for bundle 0.
Bundle systemBundle = context.getBundleContext().getBundle(0);
systemBundle.getBundleContext().addBundleListener(new SynchronousBundleListener() {
@Override
public void bundleChanged(final BundleEvent event) {
if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING) {
lifecycleState = LifeCycleState.STOPPING;
}
}
});
}
@Deactivate
protected void deactivate() {
// reset, if this component is ever reused (should normally not be the case), it should be "starting" again.
lifecycleState = LifeCycleState.STARTING;
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
protected void setDashboardStarted(DashboardReady dashboardStarted) {
this.dashboardStarted = dashboardStarted;
this.lifecycleState = LifeCycleState.STARTED;
}
protected void unsetDashboardStarted(DashboardReady dashboardStarted) {
if (lifecycleState != LifeCycleState.STOPPING) {
lifecycleState = LifeCycleState.UPDATING;
}
this.dashboardStarted = null;
}
@Reference
protected void setHttpService(HttpService httpService) {
this.httpService = httpService;
}
protected void unsetHttpService(HttpService httpService) {
this.httpService = null;
}
}

View File

@ -30,6 +30,7 @@
<module>org.openhab.ui.classicui</module>
<module>org.openhab.ui.homebuilder</module>
<module>org.openhab.ui.paperui</module>
<module>org.openhab.ui.start</module>
</modules>
<dependencies>

View File

@ -32,6 +32,8 @@
<feature>openhab-transport-http</feature>
<feature prerequisite="true">shell</feature>
<feature prerequisite="true">wrapper</feature>
<!-- This bundle needs to be started early as it registers the 404 and startup pages on Jetty -->
<bundle start-level="30">mvn:org.openhab.core/org.openhab.ui.start/${project.version}</bundle>
<bundle start-level="90">mvn:org.openhab.core/org.openhab.core/${project.version}</bundle>
<bundle>mvn:org.openhab.core/org.openhab.core.karaf/${project.version}</bundle>
<bundle>mvn:org.openhab.core/org.openhab.io.sound/${project.version}</bundle>

View File

@ -86,4 +86,10 @@
version="0.0.0"
unpack="false"/>
<plugin
id="org.openhab.ui.start"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>