Skip to content

Commit

Permalink
Introduced new ui.start bundle, which brings custom lifecycle status … (
Browse files Browse the repository at this point in the history
openhab#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 <[email protected]>
GitOrigin-RevId: 39f0e17
  • Loading branch information
kaikreuzer authored and splatch committed Jul 11, 2023
1 parent 18cd809 commit 6169268
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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 {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -166,29 +168,29 @@ 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");
if (entry != null) {
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");
if (warn != null) {
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");
Expand All @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions bundles/org.openhab.ui.start/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions bundles/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions features/openhab-core/src/main/feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
6 changes: 6 additions & 0 deletions features/p2/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

0 comments on commit 6169268

Please sign in to comment.