Skip to content

Commit

Permalink
Support sitemaps created through UI in proxy (#2178)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
bigbasec authored Feb 6, 2021
1 parent 87211d2 commit 74c4c23
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import java.net.URL;
import java.util.Base64;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
Expand All @@ -28,10 +30,12 @@
import javax.servlet.http.HttpServletRequest;
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.http.HttpHeader;
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.Sitemap;
import org.openhab.core.model.sitemap.sitemap.Video;
Expand All @@ -42,6 +46,7 @@
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.HttpContext;
import org.osgi.service.http.HttpService;
Expand Down Expand Up @@ -74,68 +79,58 @@
* @author Kai Kreuzer - Initial contribution
* @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 {

/** the alias for this servlet */
public static final String PROXY_ALIAS = "proxy";

private static final long serialVersionUID = -4716754591953017793L;
private static final String CONFIG_MAX_THREADS = "maxThreads";
private static final int DEFAULT_MAX_THREADS = 8;
public static final String ATTR_URI = ProxyServletService.class.getName() + ".URI";
public static final String ATTR_SERVLET_EXCEPTION = ProxyServletService.class.getName() + ".ProxyServletException";

private final Logger logger = LoggerFactory.getLogger(ProxyServletService.class);

private static final long serialVersionUID = -4716754591953017793L;

private Servlet impl;
private @Nullable Servlet impl;

protected HttpService httpService;
protected ItemUIRegistry itemUIRegistry;
protected ModelRepository modelRepository;
protected final HttpService httpService;
protected final ItemUIRegistry itemUIRegistry;
protected final List<SitemapProvider> sitemapProviders = new CopyOnWriteArrayList<>();

@Reference(policy = ReferencePolicy.DYNAMIC)
protected void setItemUIRegistry(ItemUIRegistry itemUIRegistry) {
@Activate
public ProxyServletService(@Reference ItemUIRegistry itemUIRegistry, @Reference HttpService httpService) {
this.itemUIRegistry = itemUIRegistry;
this.httpService = httpService;
}

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;
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addSitemapProvider(SitemapProvider provider) {
sitemapProviders.add(provider);
}

protected void unsetHttpService(HttpService httpService) {
this.httpService = null;
protected void removeSitemapProvider(SitemapProvider provider) {
sitemapProviders.remove(provider);
}

/**
* Return the async in preference to the blocking proxy servlet, if possible.
* Supported OSGi containers might only support Servlet API 2.4 (blocking only).
*/
private Servlet getImpl() {
if (impl == null) {
Servlet servlet = impl;
if (servlet == null) {
try {
ServletRequest.class.getMethod("startAsync");
impl = new AsyncProxyServlet(this);
servlet = new AsyncProxyServlet(this);
} catch (Throwable t) {
impl = new BlockingProxyServlet(this);
servlet = new BlockingProxyServlet(this);
}
impl = servlet;
}
return impl;
return servlet;
}

/**
Expand All @@ -144,13 +139,11 @@ private Servlet getImpl() {
* @param config the OSGi config, may be <code>null</code>
* @return properties to pass to servlet for initialization
*/
private Hashtable<String, String> propsFromConfig(Map<String, Object> config) {
Hashtable<String, String> props = new Hashtable<>();
private Hashtable<String, @Nullable String> propsFromConfig(Map<String, Object> config) {
Hashtable<String, @Nullable String> props = new Hashtable<>();

if (config != null) {
for (String key : config.keySet()) {
props.put(key, config.get(key).toString());
}
for (String key : config.keySet()) {
props.put(key, config.get(key).toString());
}

// must specify for Jetty proxy servlet, per http://stackoverflow.com/a/27625380
Expand All @@ -169,7 +162,7 @@ protected void activate(Map<String, Object> config) {

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());
} catch (NamespaceException | ServletException e) {
logger.error("Error during servlet startup: {}", e.getMessage());
Expand Down Expand Up @@ -218,6 +211,7 @@ public int getCode() {
* future calls.
* @return the URI indicated by the request, or <code>null</code> if not possible
*/
/* default */ @Nullable
URI uriFromRequest(HttpServletRequest request) {
try {
// Return any URI we've already saved for this request
Expand All @@ -244,7 +238,8 @@ URI uriFromRequest(HttpServletRequest request) {
"Parameter 'widgetId' must be provided!");
}

Sitemap sitemap = (Sitemap) modelRepository.getModel(sitemapName);
Sitemap sitemap = getSitemap(sitemapName);

if (sitemap == null) {
throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND,
String.format("Sitemap '%s' could not be found!", sitemapName));
Expand Down Expand Up @@ -294,6 +289,17 @@ URI uriFromRequest(HttpServletRequest request) {
}
}

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 {
// URI in this context should be valid URL. Therefore before creating URI, create URL,
// which validates the string.
Expand All @@ -307,7 +313,7 @@ private URI createURIFromString(String url) throws MalformedURLException, URISyn
* @param uri the URI which may contain user info
* @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) {
String[] userInfo = uri.getUserInfo().split(":");

Expand Down Expand Up @@ -342,7 +348,7 @@ boolean proxyingVideoWidget(HttpServletRequest request) {
"Parameter 'widgetId' must be provided!");
}

Sitemap sitemap = (Sitemap) modelRepository.getModel(sitemapName);
Sitemap sitemap = getSitemap(sitemapName);
if (sitemap == null) {
throw new ProxyServletException(HttpServletResponse.SC_NOT_FOUND,
String.format("Sitemap '%s' could not be found!", sitemapName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.OnOffType;
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.Sitemap;
import org.openhab.core.model.sitemap.sitemap.Switch;
import org.openhab.core.model.sitemap.sitemap.Video;
import org.openhab.core.types.UnDefType;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.osgi.service.http.HttpService;

/**
* Unit tests for the {@link ProxyServletService} class.
Expand Down Expand Up @@ -65,7 +66,8 @@ public class ProxyServletServiceTest {
private static ProxyServletService service;

private ItemUIRegistry itemUIRegistry;
private ModelRepository modelRepository;
private HttpService httpService;
private SitemapProvider sitemapProvider;
private Sitemap sitemap;
private HttpServletRequest request;
private Switch switchWidget;
Expand All @@ -74,15 +76,16 @@ public class ProxyServletServiceTest {

@BeforeEach
public void setUp() {
service = new ProxyServletService();

itemUIRegistry = mock(ItemUIRegistry.class);
modelRepository = mock(ModelRepository.class);
service.setModelRepository(modelRepository);
service.setItemUIRegistry(itemUIRegistry);
httpService = mock(HttpService.class);

service = new ProxyServletService(itemUIRegistry, httpService);

sitemapProvider = mock(SitemapProvider.class);
service.sitemapProviders.add(sitemapProvider);

sitemap = mock(Sitemap.class);
when(modelRepository.getModel(eq(SITEMAP_NAME))).thenReturn(sitemap);
when(sitemapProvider.getSitemap(eq(SITEMAP_NAME))).thenReturn(sitemap);

switchWidget = mock(Switch.class);
when(itemUIRegistry.getWidget(eq(sitemap), eq(SWITCH_WIDGET_ID))).thenReturn(switchWidget);
Expand Down

0 comments on commit 74c4c23

Please sign in to comment.