Support sitemaps created through UI in proxy (#2178)

* Add site provider lookup in proxy

Third attempt at this PR, but this very simply allows for the proxy to find the sitemaps for both file based as well as gui generated.

Fixes #2154
Fixes openhab/openhab-webui#763
Fixes openhab/openhab-webui#745

Also-by: Kai Kreuzer <kai@openhab.org>
Signed-off-by: Brian Homeyer <bhomeyer@gmail.com>
This commit is contained in:
bigbasec 2021-02-06 10:26:58 -05:00 committed by GitHub
parent 87211d2439
commit 74c4c2386f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 51 deletions

View File

@ -19,7 +19,9 @@ import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Base64; import java.util.Base64;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -28,10 +30,12 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.model.core.ModelRepository; import org.openhab.core.model.sitemap.SitemapProvider;
import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Image;
import org.openhab.core.model.sitemap.sitemap.Sitemap; import org.openhab.core.model.sitemap.sitemap.Sitemap;
import org.openhab.core.model.sitemap.sitemap.Video; import org.openhab.core.model.sitemap.sitemap.Video;
@ -42,6 +46,7 @@ import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference; 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.component.annotations.ReferencePolicy;
import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService; import org.osgi.service.http.HttpService;
@ -74,12 +79,14 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Initial contribution * @author Kai Kreuzer - Initial contribution
* @author John Cocula - added optional Image/Video item= support; refactored to allow use of later spec servlet * @author John Cocula - added optional Image/Video item= support; refactored to allow use of later spec servlet
*/ */
@Component(immediate = true, property = { "service.pid=org.openhab.core.proxy" }) @NonNullByDefault
@Component(immediate = true, property = { "service.pid=org.openhab.proxy" })
public class ProxyServletService extends HttpServlet { public class ProxyServletService extends HttpServlet {
/** the alias for this servlet */ /** the alias for this servlet */
public static final String PROXY_ALIAS = "proxy"; public static final String PROXY_ALIAS = "proxy";
private static final long serialVersionUID = -4716754591953017793L;
private static final String CONFIG_MAX_THREADS = "maxThreads"; private static final String CONFIG_MAX_THREADS = "maxThreads";
private static final int DEFAULT_MAX_THREADS = 8; private static final int DEFAULT_MAX_THREADS = 8;
public static final String ATTR_URI = ProxyServletService.class.getName() + ".URI"; public static final String ATTR_URI = ProxyServletService.class.getName() + ".URI";
@ -87,39 +94,25 @@ public class ProxyServletService extends HttpServlet {
private final Logger logger = LoggerFactory.getLogger(ProxyServletService.class); private final Logger logger = LoggerFactory.getLogger(ProxyServletService.class);
private static final long serialVersionUID = -4716754591953017793L; private @Nullable Servlet impl;
private Servlet impl; protected final HttpService httpService;
protected final ItemUIRegistry itemUIRegistry;
protected final List<SitemapProvider> sitemapProviders = new CopyOnWriteArrayList<>();
protected HttpService httpService; @Activate
protected ItemUIRegistry itemUIRegistry; public ProxyServletService(@Reference ItemUIRegistry itemUIRegistry, @Reference HttpService httpService) {
protected ModelRepository modelRepository;
@Reference(policy = ReferencePolicy.DYNAMIC)
protected void setItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = itemUIRegistry; this.itemUIRegistry = itemUIRegistry;
}
protected void unsetItemUIRegistry(ItemUIRegistry itemUIRegistry) {
this.itemUIRegistry = null;
}
@Reference
protected void setModelRepository(ModelRepository modelRepository) {
this.modelRepository = modelRepository;
}
protected void unsetModelRepository(ModelRepository modelRepository) {
this.modelRepository = null;
}
@Reference(policy = ReferencePolicy.DYNAMIC)
protected void setHttpService(HttpService httpService) {
this.httpService = httpService; this.httpService = httpService;
} }
protected void unsetHttpService(HttpService httpService) { @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
this.httpService = null; protected void addSitemapProvider(SitemapProvider provider) {
sitemapProviders.add(provider);
}
protected void removeSitemapProvider(SitemapProvider provider) {
sitemapProviders.remove(provider);
} }
/** /**
@ -127,15 +120,17 @@ public class ProxyServletService extends HttpServlet {
* Supported OSGi containers might only support Servlet API 2.4 (blocking only). * Supported OSGi containers might only support Servlet API 2.4 (blocking only).
*/ */
private Servlet getImpl() { private Servlet getImpl() {
if (impl == null) { Servlet servlet = impl;
if (servlet == null) {
try { try {
ServletRequest.class.getMethod("startAsync"); ServletRequest.class.getMethod("startAsync");
impl = new AsyncProxyServlet(this); servlet = new AsyncProxyServlet(this);
} catch (Throwable t) { } catch (Throwable t) {
impl = new BlockingProxyServlet(this); servlet = new BlockingProxyServlet(this);
} }
impl = servlet;
} }
return impl; return servlet;
} }
/** /**
@ -144,13 +139,11 @@ public class ProxyServletService extends HttpServlet {
* @param config the OSGi config, may be <code>null</code> * @param config the OSGi config, may be <code>null</code>
* @return properties to pass to servlet for initialization * @return properties to pass to servlet for initialization
*/ */
private Hashtable<String, String> propsFromConfig(Map<String, Object> config) { private Hashtable<String, @Nullable String> propsFromConfig(Map<String, Object> config) {
Hashtable<String, String> props = new Hashtable<>(); Hashtable<String, @Nullable String> props = new Hashtable<>();
if (config != null) { for (String key : config.keySet()) {
for (String key : config.keySet()) { props.put(key, config.get(key).toString());
props.put(key, config.get(key).toString());
}
} }
// must specify for Jetty proxy servlet, per http://stackoverflow.com/a/27625380 // must specify for Jetty proxy servlet, per http://stackoverflow.com/a/27625380
@ -169,7 +162,7 @@ public class ProxyServletService extends HttpServlet {
logger.debug("Starting up '{}' servlet at /{}", servlet.getServletInfo(), PROXY_ALIAS); logger.debug("Starting up '{}' servlet at /{}", servlet.getServletInfo(), PROXY_ALIAS);
Hashtable<String, String> props = propsFromConfig(config); Hashtable<String, @Nullable String> props = propsFromConfig(config);
httpService.registerServlet("/" + PROXY_ALIAS, servlet, props, createHttpContext()); httpService.registerServlet("/" + PROXY_ALIAS, servlet, props, createHttpContext());
} catch (NamespaceException | ServletException e) { } catch (NamespaceException | ServletException e) {
logger.error("Error during servlet startup: {}", e.getMessage()); logger.error("Error during servlet startup: {}", e.getMessage());
@ -218,6 +211,7 @@ public class ProxyServletService extends HttpServlet {
* future calls. * future calls.
* @return the URI indicated by the request, or <code>null</code> if not possible * @return the URI indicated by the request, or <code>null</code> if not possible
*/ */
/* default */ @Nullable
URI uriFromRequest(HttpServletRequest request) { URI uriFromRequest(HttpServletRequest request) {
try { try {
// Return any URI we've already saved for this request // Return any URI we've already saved for this request
@ -244,7 +238,8 @@ public class ProxyServletService extends HttpServlet {
"Parameter 'widgetId' must be provided!"); "Parameter 'widgetId' must be provided!");
} }
Sitemap sitemap = (Sitemap) modelRepository.getModel(sitemapName); Sitemap sitemap = getSitemap(sitemapName);
if (sitemap == null) { if (sitemap == null) {
throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND, throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND,
String.format("Sitemap '%s' could not be found!", sitemapName)); String.format("Sitemap '%s' could not be found!", sitemapName));
@ -294,6 +289,17 @@ public class ProxyServletService extends HttpServlet {
} }
} }
private @Nullable Sitemap getSitemap(String sitemapName) {
Sitemap sitemap = null;
for (SitemapProvider sitemapProvider : sitemapProviders) {
sitemap = sitemapProvider.getSitemap(sitemapName);
if (sitemap != null) {
break;
}
}
return sitemap;
}
private URI createURIFromString(String url) throws MalformedURLException, URISyntaxException { private URI createURIFromString(String url) throws MalformedURLException, URISyntaxException {
// URI in this context should be valid URL. Therefore before creating URI, create URL, // URI in this context should be valid URL. Therefore before creating URI, create URL,
// which validates the string. // which validates the string.
@ -307,7 +313,7 @@ public class ProxyServletService extends HttpServlet {
* @param uri the URI which may contain user info * @param uri the URI which may contain user info
* @param request the outgoing request to which an authorization header may be added * @param request the outgoing request to which an authorization header may be added
*/ */
void maybeAppendAuthHeader(URI uri, Request request) { void maybeAppendAuthHeader(@Nullable URI uri, Request request) {
if (uri != null && uri.getUserInfo() != null) { if (uri != null && uri.getUserInfo() != null) {
String[] userInfo = uri.getUserInfo().split(":"); String[] userInfo = uri.getUserInfo().split(":");
@ -342,7 +348,7 @@ public class ProxyServletService extends HttpServlet {
"Parameter 'widgetId' must be provided!"); "Parameter 'widgetId' must be provided!");
} }
Sitemap sitemap = (Sitemap) modelRepository.getModel(sitemapName); Sitemap sitemap = getSitemap(sitemapName);
if (sitemap == null) { if (sitemap == null) {
throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND, throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND,
String.format("Sitemap '%s' could not be found!", sitemapName)); String.format("Sitemap '%s' could not be found!", sitemapName));

View File

@ -28,13 +28,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.model.core.ModelRepository; import org.openhab.core.model.sitemap.SitemapProvider;
import org.openhab.core.model.sitemap.sitemap.Image; import org.openhab.core.model.sitemap.sitemap.Image;
import org.openhab.core.model.sitemap.sitemap.Sitemap; import org.openhab.core.model.sitemap.sitemap.Sitemap;
import org.openhab.core.model.sitemap.sitemap.Switch; import org.openhab.core.model.sitemap.sitemap.Switch;
import org.openhab.core.model.sitemap.sitemap.Video; import org.openhab.core.model.sitemap.sitemap.Video;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.openhab.core.ui.items.ItemUIRegistry; import org.openhab.core.ui.items.ItemUIRegistry;
import org.osgi.service.http.HttpService;
/** /**
* Unit tests for the {@link ProxyServletService} class. * Unit tests for the {@link ProxyServletService} class.
@ -65,7 +66,8 @@ public class ProxyServletServiceTest {
private static ProxyServletService service; private static ProxyServletService service;
private ItemUIRegistry itemUIRegistry; private ItemUIRegistry itemUIRegistry;
private ModelRepository modelRepository; private HttpService httpService;
private SitemapProvider sitemapProvider;
private Sitemap sitemap; private Sitemap sitemap;
private HttpServletRequest request; private HttpServletRequest request;
private Switch switchWidget; private Switch switchWidget;
@ -74,15 +76,16 @@ public class ProxyServletServiceTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
service = new ProxyServletService();
itemUIRegistry = mock(ItemUIRegistry.class); itemUIRegistry = mock(ItemUIRegistry.class);
modelRepository = mock(ModelRepository.class); httpService = mock(HttpService.class);
service.setModelRepository(modelRepository);
service.setItemUIRegistry(itemUIRegistry); service = new ProxyServletService(itemUIRegistry, httpService);
sitemapProvider = mock(SitemapProvider.class);
service.sitemapProviders.add(sitemapProvider);
sitemap = mock(Sitemap.class); sitemap = mock(Sitemap.class);
when(modelRepository.getModel(eq(SITEMAP_NAME))).thenReturn(sitemap); when(sitemapProvider.getSitemap(eq(SITEMAP_NAME))).thenReturn(sitemap);
switchWidget = mock(Switch.class); switchWidget = mock(Switch.class);
when(itemUIRegistry.getWidget(eq(sitemap), eq(SWITCH_WIDGET_ID))).thenReturn(switchWidget); when(itemUIRegistry.getWidget(eq(sitemap), eq(SWITCH_WIDGET_ID))).thenReturn(switchWidget);