diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ColorpickerRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ColorpickerRenderer.java index cd89af592f..4de35693ab 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ColorpickerRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ColorpickerRenderer.java @@ -87,7 +87,7 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th snippet = snippet.replace("%purelabel%", purelabel); } snippet = snippet.replace("%frequency%", frequency); - snippet = snippet.replace("%servletname%", WebAppServlet.SERVLET_NAME); + snippet = snippet.replace("%servletname%", WebAppServlet.SERVLET_PATH); String style = ""; String color = itemUIRegistry.getLabelColor(w); diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java index c2752038f0..d1db8ed37e 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java @@ -111,7 +111,7 @@ public StringBuilder processPage(String id, String sitemap, String label, EList< labelPlain = labelPlain.replace("[", "").replace("]", ""); } snippet = snippet.replace("%label%", escapeHtml(labelPlain)); - snippet = snippet.replace("%servletname%", WebAppServlet.SERVLET_NAME); + snippet = snippet.replace("%servletname%", WebAppServlet.SERVLET_PATH); snippet = snippet.replace("%sitemap%", sitemap); snippet = snippet.replace("%htmlclass%", config.getCssClassList()); snippet = snippet.replace("%icon_type%", ICON_TYPE); diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/BaseServlet.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/BaseServlet.java deleted file mode 100644 index 3ca1202df2..0000000000 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/BaseServlet.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.ui.basic.internal.servlet; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.io.http.HttpContextFactoryService; -import org.openhab.core.io.http.servlet.OpenHABBundleServlet; -import org.openhab.core.items.ItemRegistry; -import org.osgi.service.http.HttpService; - -/** - * This is the base servlet class for other servlet in the Basic UI. - * - * @author Thomas.Eichstaedt-Engelen - Initial contribution - */ -@NonNullByDefault -public abstract class BaseServlet extends OpenHABBundleServlet { - - private static final long serialVersionUID = -4012800772403491132L; - - protected final ItemRegistry itemRegistry; - - /** the root path of this web application */ - public static final String WEBAPP_ALIAS = "/basicui"; - - public BaseServlet(HttpService httpService, HttpContextFactoryService httpContextFactoryService, - ItemRegistry itemRegistry) { - super(httpService, httpContextFactoryService); - this.itemRegistry = itemRegistry; - } -} diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/CmdServlet.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/CmdServlet.java index 77ea4ff50d..fe7b548f19 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/CmdServlet.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/CmdServlet.java @@ -14,13 +14,14 @@ import java.io.IOException; +import javax.servlet.Servlet; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.events.EventPublisher; -import org.openhab.core.io.http.HttpContextFactoryService; import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; @@ -30,12 +31,15 @@ import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.Command; import org.openhab.core.types.TypeParser; -import org.osgi.framework.BundleContext; 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.http.HttpService; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardResource; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletAsyncSupported; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This servlet receives events from the web app and sends these as @@ -45,32 +49,27 @@ * @author Stefan Bußweiler - Migration to new ESH event concept * */ -@Component(immediate = true, service = {}) +@Component(immediate = true, service = Servlet.class) +@HttpWhiteboardResource(pattern = "/basicui/*", prefix = "/web") +@HttpWhiteboardServletAsyncSupported(asyncSupported = true) +@HttpWhiteboardServletName(CmdServlet.SERVLET_PATH) +@HttpWhiteboardServletPattern(CmdServlet.SERVLET_PATH + "/*") @NonNullByDefault -public class CmdServlet extends BaseServlet { +public class CmdServlet extends HttpServlet { private static final long serialVersionUID = 4813813926991230571L; - public static final String SERVLET_NAME = "CMD"; + public static final String SERVLET_PATH = "/basicui/CMD"; + + private final Logger logger = LoggerFactory.getLogger(CmdServlet.class); private final EventPublisher eventPublisher; + private final ItemRegistry itemRegistry; @Activate - public CmdServlet(final @Reference HttpService httpService, - final @Reference HttpContextFactoryService httpContextFactoryService, - final @Reference ItemRegistry itemRegistry, final @Reference EventPublisher eventPublisher) { - super(httpService, httpContextFactoryService, itemRegistry); + public CmdServlet(final @Reference ItemRegistry itemRegistry, final @Reference EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; - } - - @Activate - protected void activate(BundleContext bundleContext) { - super.activate(WEBAPP_ALIAS + "/" + SERVLET_NAME, bundleContext); - } - - @Deactivate - protected void deactivate() { - httpService.unregister(WEBAPP_ALIAS + "/" + SERVLET_NAME); + this.itemRegistry = itemRegistry; } @Override diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/ManifestServlet.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/ManifestServlet.java index 61bebfeeb1..dfe085ada7 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/ManifestServlet.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/ManifestServlet.java @@ -14,42 +14,43 @@ import java.io.IOException; import java.io.PrintWriter; -import java.util.Map; +import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.io.http.HttpContextFactoryService; -import org.openhab.core.items.ItemRegistry; import org.openhab.ui.basic.internal.render.PageRenderer; import org.openhab.ui.basic.render.RenderException; -import org.osgi.framework.BundleContext; 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.http.HttpContext; -import org.osgi.service.http.HttpService; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletAsyncSupported; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This is the manifest servlet for the Basic UI. It dynamically genrates a + * This is the manifest servlet for the Basic UI. It dynamically generates a * manifest on the sitemap model. * * @author Kevin Haunschmied - Initial contribution * */ -@Component(immediate = true, service = {}) +@Component(immediate = true, service = Servlet.class) @NonNullByDefault -public class ManifestServlet extends BaseServlet { +@HttpWhiteboardServletAsyncSupported(asyncSupported = true) +@HttpWhiteboardServletName(ManifestServlet.WEBAPP_ALIAS) +@HttpWhiteboardServletPattern(ManifestServlet.WEBAPP_ALIAS + "/" + ManifestServlet.MANIFEST_NAME) +public class ManifestServlet extends HttpServlet { private static final long serialVersionUID = 4591967180619528326L; - + public static final String WEBAPP_ALIAS = "/basicui"; public static final String MANIFEST_NAME = "manifest.json"; private static final String MANIFEST_CONTENT_TYPE = "application/json;charset=UTF-8"; @@ -59,24 +60,10 @@ public class ManifestServlet extends BaseServlet { private final PageRenderer renderer; @Activate - public ManifestServlet(final @Reference HttpService httpService, - final @Reference HttpContextFactoryService httpContextFactoryService, - final @Reference ItemRegistry itemRegistry, final @Reference PageRenderer renderer) { - super(httpService, httpContextFactoryService, itemRegistry); + public ManifestServlet(final @Reference PageRenderer renderer) { this.renderer = renderer; } - @Activate - protected void activate(Map configProps, BundleContext bundleContext) { - HttpContext httpContext = createHttpContext(bundleContext.getBundle()); - super.activate(WEBAPP_ALIAS + "/" + MANIFEST_NAME, httpContext); - } - - @Deactivate - protected void deactivate() { - super.deactivate(WEBAPP_ALIAS + "/" + MANIFEST_NAME); - } - private void generateManifest(ServletResponse res, @Nullable String sitemapName) throws IOException, RenderException { PrintWriter resWriter; diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/WebAppServlet.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/WebAppServlet.java index 9d61a53d18..52a76fdf57 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/WebAppServlet.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/servlet/WebAppServlet.java @@ -21,15 +21,14 @@ import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.emf.common.util.EList; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.config.core.ConfigurableService; -import org.openhab.core.io.http.HttpContextFactoryService; import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService; -import org.openhab.core.items.ItemRegistry; import org.openhab.core.model.sitemap.SitemapProvider; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Sitemap; @@ -46,9 +45,9 @@ 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.HttpContext; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletAsyncSupported; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,8 +62,11 @@ @Component(immediate = true, service = Servlet.class, configurationPid = "org.openhab.basicui", // property = Constants.SERVICE_PID + "=org.openhab.basicui") @ConfigurableService(category = "ui", label = "Basic UI", description_uri = WebAppServlet.CONFIG_URI) +@HttpWhiteboardServletAsyncSupported(asyncSupported = true) +@HttpWhiteboardServletName(WebAppServlet.SERVLET_PATH) +@HttpWhiteboardServletPattern(WebAppServlet.SERVLET_PATH + "/*") @NonNullByDefault -public class WebAppServlet extends BaseServlet { +public class WebAppServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(WebAppServlet.class); @@ -73,7 +75,7 @@ public class WebAppServlet extends BaseServlet { protected static final String CONFIG_URI = "ui:basic"; /** the name of the servlet to be used in the URL */ - public static final String SERVLET_NAME = "app"; + public static final String SERVLET_PATH = "/basicui/app"; private static final String CONTENT_TYPE_ASYNC = "application/xml;charset=UTF-8"; private static final String CONTENT_TYPE = "text/html;charset=UTF-8"; @@ -84,11 +86,8 @@ public class WebAppServlet extends BaseServlet { protected final Set sitemapProviders = new CopyOnWriteArraySet<>(); @Activate - public WebAppServlet(final @Reference HttpService httpService, - final @Reference HttpContextFactoryService httpContextFactoryService, - final @Reference ItemRegistry itemRegistry, final @Reference SitemapSubscriptionService subscriptions, + public WebAppServlet(final @Reference SitemapSubscriptionService subscriptions, final @Reference PageRenderer renderer) { - super(httpService, httpContextFactoryService, itemRegistry); this.subscriptions = subscriptions; this.renderer = renderer; } @@ -104,15 +103,6 @@ public void removeSitemapProvider(SitemapProvider sitemapProvider) { @Activate protected void activate(Map configProps, BundleContext bundleContext) { - HttpContext httpContext = createHttpContext(bundleContext.getBundle()); - super.activate(WEBAPP_ALIAS + "/" + SERVLET_NAME, httpContext); - - try { - httpService.registerResources(WEBAPP_ALIAS, "web", httpContext); - } catch (NamespaceException e) { - logger.error("Could not register static resources under {}", WEBAPP_ALIAS, e); - } - modified(configProps); } @@ -124,8 +114,6 @@ protected void modified(Map configProps) { @Deactivate protected void deactivate() { - super.deactivate(WEBAPP_ALIAS + "/" + SERVLET_NAME); - httpService.unregister(WEBAPP_ALIAS); logger.info("Stopped Basic UI"); } diff --git a/bundles/org.openhab.ui.habpanel/src/main/java/org/openhab/ui/habpanel/internal/HABPanelTile.java b/bundles/org.openhab.ui.habpanel/src/main/java/org/openhab/ui/habpanel/internal/HABPanelTile.java index 47d72d3cdb..79619e6e9c 100755 --- a/bundles/org.openhab.ui.habpanel/src/main/java/org/openhab/ui/habpanel/internal/HABPanelTile.java +++ b/bundles/org.openhab.ui.habpanel/src/main/java/org/openhab/ui/habpanel/internal/HABPanelTile.java @@ -18,9 +18,7 @@ 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.http.HttpService; -import org.osgi.service.http.NamespaceException; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,29 +29,21 @@ * */ @Component(service = Tile.class, immediate = true) +@HttpWhiteboardResource(pattern = HABPanelTile.RESOURCE_PATH + "/*", prefix = "/web") @NonNullByDefault public class HABPanelTile implements Tile { - public static final String HABPANEL_ALIAS = "/habpanel"; + public static final String RESOURCE_PATH = "/habpanel"; private final Logger logger = LoggerFactory.getLogger(HABPanelTile.class); - private final HttpService httpService; - @Activate - public HABPanelTile(final @Reference HttpService httpService) { - this.httpService = httpService; - try { - httpService.registerResources(HABPANEL_ALIAS, "web", null); - logger.info("Started HABPanel at {}", HABPANEL_ALIAS); - } catch (NamespaceException e) { - logger.error("Error during HABPanel startup: {}", e.getMessage()); - } + public HABPanelTile() { + logger.info("Started HABPanel at {}", RESOURCE_PATH); } @Deactivate protected void deactivate() { - httpService.unregister(HABPANEL_ALIAS); logger.info("Stopped HABPanel"); } @@ -64,7 +54,7 @@ public String getName() { @Override public String getUrl() { - return "/habpanel/index.html"; + return RESOURCE_PATH + "/index.html"; } @Override @@ -74,6 +64,6 @@ public String getUrl() { @Override public String getImageUrl() { - return "/habpanel/tile.png"; + return RESOURCE_PATH + "/tile.png"; } } diff --git a/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIContext.java b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIContext.java new file mode 100644 index 0000000000..3a5c622802 --- /dev/null +++ b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIContext.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.ui.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.http.context.ServletContextHelper; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; + +/** + * The {@link UIContext} is the shared context for Main UI servlets + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(property = { HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=org.openhab.ui.context", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=/", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + "acceptRanges=true", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + "dirAllowed=false", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + "redirectWelcome=false", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + "preCompressed=true", + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + + "etags=true" }, service = ServletContextHelper.class) +public class UIContext extends ServletContextHelper { +} diff --git a/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIErrorPageServlet.java b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIErrorPageServlet.java new file mode 100644 index 0000000000..af7e121fa7 --- /dev/null +++ b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIErrorPageServlet.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.ui.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletErrorPage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Returns the index file whenever pages cannot be found. This allows the UI to route to a page based on the URL or + * display an error message. The response status is based on the first path segment of the request to keep the logic + * simple. + * + * @author Wouter Born - Initial contribution + */ +@Component(immediate = true, service = Servlet.class) +@HttpWhiteboardServletErrorPage(errorPage = { "404" }) +@NonNullByDefault +public class UIErrorPageServlet extends HttpServlet { + + private static final long serialVersionUID = 6472170444750947727L; + + private static final String INDEX_FILE = "/app/index.html"; + private static final Set VALID_ROUTE_SEGMENTS = Set.of("about", "analyzer", "developer", "home", "page", + "profile", "res", "settings", "setup-wizard"); + + private final Logger logger = LoggerFactory.getLogger(UIErrorPageServlet.class); + + @Override + protected void doGet(@NonNullByDefault({}) HttpServletRequest req, @NonNullByDefault({}) HttpServletResponse resp) + throws ServletException, IOException { + try (InputStream is = UIErrorPageServlet.class.getResourceAsStream(INDEX_FILE)) { + if (is == null) { + logger.warn("The index file ({}) does not exist", INDEX_FILE); + return; + } + String requestURI = (String) req.getAttribute("javax.servlet.error.request_uri"); + int status = isValidRoute(requestURI) ? 200 : 404; + logger.debug("Returning index file as response with status {} for request URI: {}", status, requestURI); + resp.setContentType("text/html"); + resp.setStatus(status); + is.transferTo(resp.getOutputStream()); + } + } + + private boolean isValidRoute(@Nullable String path) { + if (path == null) { + return false; + } + + String[] segments = path.split("/"); + return segments.length > 1 && segments[0].isEmpty() && VALID_ROUTE_SEGMENTS.contains(segments[1]); + } +} diff --git a/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIServlet.java b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIServlet.java index fb6193a5d7..df6a447dc7 100644 --- a/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIServlet.java +++ b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIServlet.java @@ -19,9 +19,8 @@ import java.net.URL; import java.nio.channels.ReadableByteChannel; import java.util.Arrays; -import java.util.Hashtable; -import java.util.Map; +import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServletRequest; @@ -33,13 +32,14 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.util.resource.Resource; import org.openhab.core.OpenHAB; +import org.ops4j.pax.web.service.WebContainer; 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.http.HttpContext; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContext; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,9 +50,11 @@ * @author Yannick Schaus - Initial contribution * @author Dan Cunningham - Convert file serving to a custom servlet */ - -@Component(immediate = true, name = "org.openhab.ui", property = { "httpContext.id:String=oh-ui-http-ctx" }) +@Component(immediate = true, service = Servlet.class, name = "org.openhab.ui") @NonNullByDefault +@HttpWhiteboardServletName(UIServlet.SERVLET_PATH) +@HttpWhiteboardServletPattern(UIServlet.SERVLET_PATH + "*") +@HttpWhiteboardContext(name = "=org.openhab.ui.context", path = "=/") public class UIServlet extends DefaultServlet { private static final long serialVersionUID = 1L; @@ -60,48 +62,24 @@ public class UIServlet extends DefaultServlet { private final Logger logger = LoggerFactory.getLogger(UIServlet.class); private static final String APP_BASE = "app"; - private static final String SERVLET_NAME = "/"; + public static final String SERVLET_PATH = "/"; private static final String STATIC_PATH = "/static"; private static final String STATIC_BASE = OpenHAB.getConfigFolder() + "/html"; private static final String[] COMPRESS_EXT = { "gz", "br" }; private final HttpContext defaultHttpContext; - private final HttpService httpService; - private long bundleModifiedTime = 0; + private final long bundleModifiedTime; private @Nullable ContextHandler contextHandler; @Activate - public UIServlet(final @Reference HttpService httpService, final @Reference HttpContext httpContext) { - super(); - defaultHttpContext = httpService.createDefaultHttpContext(); - this.httpService = httpService; - } - - @Activate - protected void activate(Map config) { - try { - logger.debug("Starting up {} at {}", getClass().getSimpleName(), SERVLET_NAME); - httpService.registerServlet(SERVLET_NAME, this, new Hashtable<>(), defaultHttpContext); - bundleModifiedTime = (System.currentTimeMillis() / 1000) * 1000; // round milliseconds - } catch (NamespaceException e) { - logger.error("Error during servlet registration - alias {} already in use", SERVLET_NAME, e); - } catch (ServletException e) { - logger.error("Error during servlet registration", e); - } - } - - @Deactivate - protected void deactivate() { - httpService.unregister(SERVLET_NAME); + public UIServlet(final @Reference WebContainer webContainer) { + this.defaultHttpContext = webContainer.createDefaultHttpContext(); + logger.debug("Starting up {} at {}", getClass().getSimpleName(), SERVLET_PATH); + bundleModifiedTime = (System.currentTimeMillis() / 1000) * 1000; // round milliseconds } @Override public void init() throws UnavailableException { - setInitParameter("acceptRanges", "true"); - setInitParameter("dirAllowed", "false"); - setInitParameter("redirectWelcome", "false"); - setInitParameter("precompressed", "true"); - setInitParameter("etags", "true"); contextHandler = ContextHandler.getCurrentContext().getContextHandler(); super.init(); } @@ -117,7 +95,7 @@ public void init() throws UnavailableException { if (name.startsWith(STATIC_PATH) && !name.endsWith("/")) { URL url = null; try { - url = new java.io.File(STATIC_BASE + name.substring(new String(STATIC_PATH).length())).toURI().toURL(); + url = new File(STATIC_BASE + name.substring(STATIC_PATH.length())).toURI().toURL(); } catch (MalformedURLException e) { logger.error("Error while serving static content: {}", e.getMessage()); url = defaultHttpContext.getResource(APP_BASE + name); @@ -131,12 +109,11 @@ public void init() throws UnavailableException { } } else { URL url = defaultHttpContext.getResource(APP_BASE + name); - // if we don't find a resource, and its not a check for a compressed version, return the app base and let + // if we don't find a resource, and it's not a check for a compressed version, + // let the UIErrorPageServlet return the app base with a proper response code that lets // the Vue.js main UI handle routing - if (url == null && !Arrays.stream(COMPRESS_EXT).anyMatch(entry -> name.endsWith(entry))) { - // sending a directory will trigger "getWelcomeFile" to be called which is required to avoid - // Jetty doing a 302 redirect which breaks Vue.js routing when reloading the browser on some pages - url = defaultHttpContext.getResource(APP_BASE); + if (url == null && Arrays.stream(COMPRESS_EXT).noneMatch(name::endsWith)) { + return null; } try { logger.debug("getResource bundle file returning {}", url); @@ -170,10 +147,6 @@ protected void doGet(@Nullable HttpServletRequest request, @Nullable HttpServlet return "/index.html"; } - private void setInitParameter(String name, String value) { - getServletContext().setInitParameter(CONTEXT_INIT + name, value); - } - /** * * Wraps a resource and returns a consistent time for bundled files @@ -181,7 +154,7 @@ private void setInitParameter(String name, String value) { */ class ResourceWrapper extends Resource { - private Resource baseResource; + private final Resource baseResource; public ResourceWrapper(Resource baseResource) { this.baseResource = baseResource; @@ -258,8 +231,13 @@ public String[] list() { } @Override - public Resource addPath(@Nullable String path) throws IOException, MalformedURLException { + public Resource addPath(@Nullable String path) throws IOException { return baseResource.addPath(path); } + + @Override + public String toString() { + return getURL().toString(); + } } } diff --git a/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIWelcomeFileMapping.java b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIWelcomeFileMapping.java new file mode 100644 index 0000000000..a94919eb6f --- /dev/null +++ b/bundles/org.openhab.ui/src/main/java/org/openhab/ui/internal/UIWelcomeFileMapping.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.ui.internal; + +import org.ops4j.pax.web.extender.whiteboard.runtime.DefaultWelcomeFileMapping; +import org.ops4j.pax.web.service.whiteboard.WelcomeFileMapping; +import org.osgi.service.component.annotations.Component; + +/** + * The mapping of files considered as welcome file when there is no file name in requests. + * + * @author Wouter Born - Initial contribution + */ +@Component(service = WelcomeFileMapping.class) +public class UIWelcomeFileMapping extends DefaultWelcomeFileMapping { + + public UIWelcomeFileMapping() { + setRedirect(false); + setWelcomeFiles(new String[] { "index.html" }); + } +} diff --git a/pom.xml b/pom.xml index 6e2f58912b..9d1f6832cd 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 6.4.0 2.3.0 2.14.1 - 4.3.7 + 4.4.3 4.0.0-SNAPSHOT 0.13.0 2.28.0