diff --git a/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/DashboardReady.java b/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/DashboardReady.java
new file mode 100644
index 00000000000..e4e0a49053e
--- /dev/null
+++ b/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/DashboardReady.java
@@ -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 {
+
+}
diff --git a/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/internal/DashboardService.java b/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/internal/DashboardService.java
index 8f37c44c6f2..9b3ad0e648f 100644
--- a/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/internal/DashboardService.java
+++ b/bundles/org.openhab.ui.dashboard/src/main/java/org/openhab/ui/dashboard/internal/DashboardService.java
@@ -24,10 +24,12 @@
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 @@
* @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 @@ protected HttpServlet createServlet() {
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 @@ protected HttpServlet createServlet() {
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 @@ protected HttpServlet createServlet() {
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 @@ protected HttpServlet createServlet() {
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,
diff --git a/bundles/org.openhab.ui.start/pom.xml b/bundles/org.openhab.ui.start/pom.xml
new file mode 100644
index 00000000000..e6ba16c3ca1
--- /dev/null
+++ b/bundles/org.openhab.ui.start/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ org.openhab.core
+ pom-bundles
+ 2.4.0-SNAPSHOT
+
+
+ org.openhab.ui.start
+
+ eclipse-plugin
+ openHAB Start UI
+
diff --git a/bundles/org.openhab.ui.start/src/main/java/org/openhab/ui/start/internal/RootServlet.java b/bundles/org.openhab.ui.start/src/main/java/org/openhab/ui/start/internal/RootServlet.java
new file mode 100644
index 00000000000..61980bdc705
--- /dev/null
+++ b/bundles/org.openhab.ui.start/src/main/java/org/openhab/ui/start/internal/RootServlet.java
@@ -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;
+ }
+}
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 81f01e935cc..0cf6d160d3e 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -30,6 +30,7 @@
org.openhab.ui.classicui
org.openhab.ui.homebuilder
org.openhab.ui.paperui
+ org.openhab.ui.start
diff --git a/features/openhab-core/src/main/feature/feature.xml b/features/openhab-core/src/main/feature/feature.xml
index b2d22351683..414cff95940 100644
--- a/features/openhab-core/src/main/feature/feature.xml
+++ b/features/openhab-core/src/main/feature/feature.xml
@@ -32,6 +32,8 @@
openhab-transport-http
shell
wrapper
+
+ mvn:org.openhab.core/org.openhab.ui.start/${project.version}
mvn:org.openhab.core/org.openhab.core/${project.version}
mvn:org.openhab.core/org.openhab.core.karaf/${project.version}
mvn:org.openhab.core/org.openhab.io.sound/${project.version}
diff --git a/features/p2/feature.xml b/features/p2/feature.xml
index 075d7889e44..5679122e3cf 100644
--- a/features/p2/feature.xml
+++ b/features/p2/feature.xml
@@ -86,4 +86,10 @@
version="0.0.0"
unpack="false"/>
+