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"/> +