From 442b0feca9f556a501f3b72e4d6d6ad14b6616d5 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Thu, 22 Jan 2026 10:56:57 -0800 Subject: [PATCH 1/2] Make select inactive modules unreachable --- api/src/org/labkey/api/module/DefaultModule.java | 11 +++++++++-- api/src/org/labkey/api/module/Module.java | 9 +++++++++ api/src/org/labkey/api/view/HttpView.java | 4 ++-- api/src/org/labkey/api/view/JspView.java | 12 ++++++------ api/src/org/labkey/api/view/PopupAdminView.java | 1 + api/src/org/labkey/api/view/ServletView.java | 3 +-- api/src/org/labkey/api/view/ViewContext.java | 7 +++---- .../src/org/labkey/mothership/MothershipModule.java | 6 ++++++ 8 files changed, 37 insertions(+), 16 deletions(-) diff --git a/api/src/org/labkey/api/module/DefaultModule.java b/api/src/org/labkey/api/module/DefaultModule.java index 19176ad6412..b3d23f36f86 100644 --- a/api/src/org/labkey/api/module/DefaultModule.java +++ b/api/src/org/labkey/api/module/DefaultModule.java @@ -1109,6 +1109,13 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response, A ViewContext rootContext = new ViewContext(request, response, url); + Container container = rootContext.getContainer(); + if (container != null && isAvailableOnlyWhenActive() && !container.getActiveModules().contains(this)) + { + ExceptionUtil.handleException(request, response, new NotFoundException("Module " + getName() + " is not active in " + container.getPath()), null, false); + return; + } + try (var ignored =HttpView.initForRequest(rootContext, request, response)) { response.setContentType("text/html;charset=UTF-8"); @@ -1125,8 +1132,8 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response, A } request.setAttribute(ViewServlet.REQUEST_ACTION_URL, url); - if (controller instanceof HasViewContext) - ((HasViewContext)controller).setViewContext(rootContext); + if (controller instanceof HasViewContext hvc) + hvc.setViewContext(rootContext); controller.handleRequest(request, response); } catch (ServletException | IOException x) diff --git a/api/src/org/labkey/api/module/Module.java b/api/src/org/labkey/api/module/Module.java index e64bb9fe929..7949dce1847 100644 --- a/api/src/org/labkey/api/module/Module.java +++ b/api/src/org/labkey/api/module/Module.java @@ -69,6 +69,15 @@ default void registerFilters(ServletContext servletCtx) {} default boolean isUnderResourcesDirectory(java.nio.file.Path path) { return false; } + /** + * If true, the module is considered unreachable in any container where it's not active/enabled. Its controllers + * will not respond to requests, and it will not show in the admin menu's module list. + * + * More modules should return true, but historically we haven't been especially mindful of service-type modules + * (such as query or pipeline) being active in all containers where they're being used. + */ + default boolean isAvailableOnlyWhenActive() { return false; } + enum TabDisplayMode { DISPLAY_NEVER, diff --git a/api/src/org/labkey/api/view/HttpView.java b/api/src/org/labkey/api/view/HttpView.java index e4f39169f3e..c672b62c678 100644 --- a/api/src/org/labkey/api/view/HttpView.java +++ b/api/src/org/labkey/api/view/HttpView.java @@ -119,7 +119,7 @@ public final void render(ModelBean model, HttpServletRequest request, HttpServle { if (response.getContentType() != null && !response.getContentType().contains("charset")) response.setContentType(response.getContentType()+"; charset=UTF-8"); - try (var closeable = HttpView.pushView(this, request, response, null)) + try (var _ = HttpView.pushView(this, request, response, null)) { initViewContext(getViewContext(), request, response); renderInternal(model, request, response); @@ -752,7 +752,7 @@ public static PropertyValues getBindPropertyValues() ModelAndView mv = vse.mv; if (mv instanceof HasViewContext) { - ViewContext context = ((HttpView)mv).getViewContext(); + ViewContext context = ((HttpView)mv).getViewContext(); if (context._pvsBind != null) return context._pvsBind; } diff --git a/api/src/org/labkey/api/view/JspView.java b/api/src/org/labkey/api/view/JspView.java index ebe6a08182c..617e8db6b6b 100644 --- a/api/src/org/labkey/api/view/JspView.java +++ b/api/src/org/labkey/api/view/JspView.java @@ -17,6 +17,7 @@ package org.labkey.api.view; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.action.HasViewContext; @@ -37,7 +38,6 @@ import jakarta.servlet.http.HttpSession; import jakarta.servlet.jsp.HttpJspPage; import java.util.Map; -import java.util.Set; public class JspView extends WebPartView @@ -133,11 +133,11 @@ protected void renderView(ModelClass model, HttpServletRequest request, HttpServ boolean devMode = AppProps.getInstance().isDevMode() || (_viewContext != null && _viewContext.getUser() != null && _viewContext.getContainer() != null && _viewContext.getUser().isPlatformDeveloper()); - boolean isDebugHtml = devMode && this.getFrame() != FrameType.NOT_HTML && StringUtils.startsWith(response.getContentType(), "text/html"); + boolean isDebugHtml = devMode && this.getFrame() != FrameType.NOT_HTML && Strings.CI.startsWith(response.getContentType(), "text/html"); if (isDebugHtml) response.getWriter().print(""); - try (Timing t = MiniProfiler.step(_page.getClass().getSimpleName())) + try (Timing _ = MiniProfiler.step(_page.getClass().getSimpleName())) { _page._jspService(request, response); } @@ -194,10 +194,10 @@ protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest requ if (model != null) context.getExtendedProperties().putAll(model); - for (Map.Entry e : (Set)context.getExtendedProperties().entrySet()) + for (Map.Entry e : context.getExtendedProperties().entrySet()) { - if (e.getKey() instanceof String && e.getValue() != null) - request.setAttribute((String)e.getKey(), e.getValue()); + if (e.getKey() != null && e.getValue() != null) + request.setAttribute(e.getKey(), e.getValue()); } } } diff --git a/api/src/org/labkey/api/view/PopupAdminView.java b/api/src/org/labkey/api/view/PopupAdminView.java index d9ea2ffe6b5..e2d2f038a13 100644 --- a/api/src/org/labkey/api/view/PopupAdminView.java +++ b/api/src/org/labkey/api/view/PopupAdminView.java @@ -135,6 +135,7 @@ public static NavTree createNavTree(final ViewContext context) SortedSet disabledModules = new TreeSet<>(moduleComparator); disabledModules.addAll(ModuleLoader.getInstance().getModules()); disabledModules.removeAll(activeModules); + disabledModules.removeIf(Module::isAvailableOnlyWhenActive); NavTree goToModuleMenu = new NavTree("Go To Module"); Module defaultModule = null; diff --git a/api/src/org/labkey/api/view/ServletView.java b/api/src/org/labkey/api/view/ServletView.java index 4090a74ff42..23d8eb1835b 100644 --- a/api/src/org/labkey/api/view/ServletView.java +++ b/api/src/org/labkey/api/view/ServletView.java @@ -58,9 +58,8 @@ public String getPath() public void renderInternal(Object model, HttpServletRequest request, HttpServletResponse response) throws Exception { ViewContext context = getViewContext(); - for (Object o : context.entrySet()) + for (Map.Entry entry : context.entrySet()) { - Map.Entry entry = (Map.Entry) o; request.setAttribute(String.valueOf(entry.getKey()), entry.getValue()); } diff --git a/api/src/org/labkey/api/view/ViewContext.java b/api/src/org/labkey/api/view/ViewContext.java index c6e289e2c10..23f87d60e18 100644 --- a/api/src/org/labkey/api/view/ViewContext.java +++ b/api/src/org/labkey/api/view/ViewContext.java @@ -116,9 +116,8 @@ public ViewContext(HttpServletRequest request, HttpServletResponse response, Act setRequest(request); setResponse(response); - for (Object o : _request.getParameterMap().entrySet()) + for (Map.Entry entry : _request.getParameterMap().entrySet()) { - Map.Entry entry = (Map.Entry) o; String key = entry.getKey(); String[] value = entry.getValue(); @@ -126,7 +125,7 @@ public ViewContext(HttpServletRequest request, HttpServletResponse response, Act _map.put(key, value[0]); else { - List list = new ArrayList<>(Arrays.asList(value)); + List list = new ArrayList<>(Arrays.asList(value)); _map.put(key, list); } } @@ -193,7 +192,7 @@ public static ViewContext getMockViewContext(User user, Container c, ActionURL u // =========================================== // Last vestiges of ViewContext implementing BoundMap/Map below. TODO: Remove these methods - public Map getExtendedProperties() + public Map getExtendedProperties() { return _map; } diff --git a/mothership/src/org/labkey/mothership/MothershipModule.java b/mothership/src/org/labkey/mothership/MothershipModule.java index a9d1da31890..c47f4573207 100644 --- a/mothership/src/org/labkey/mothership/MothershipModule.java +++ b/mothership/src/org/labkey/mothership/MothershipModule.java @@ -87,6 +87,12 @@ public boolean hasScripts() return true; } + @Override + public boolean isAvailableOnlyWhenActive() + { + return true; + } + @Override public void afterUpdate(ModuleContext moduleContext) { From b22fab1a50dbeb25913250a2a97dbfc4290b1c2f Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 23 Jan 2026 18:19:29 -0800 Subject: [PATCH 2/2] Give modules autonomy for availability --- api/src/org/labkey/api/module/DefaultModule.java | 2 +- api/src/org/labkey/api/module/Module.java | 8 +++----- api/src/org/labkey/api/view/PopupAdminView.java | 2 +- .../src/org/labkey/mothership/MothershipModule.java | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/api/src/org/labkey/api/module/DefaultModule.java b/api/src/org/labkey/api/module/DefaultModule.java index b3d23f36f86..2e9c6a7956d 100644 --- a/api/src/org/labkey/api/module/DefaultModule.java +++ b/api/src/org/labkey/api/module/DefaultModule.java @@ -1110,7 +1110,7 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response, A ViewContext rootContext = new ViewContext(request, response, url); Container container = rootContext.getContainer(); - if (container != null && isAvailableOnlyWhenActive() && !container.getActiveModules().contains(this)) + if (container != null && !isAvailable(container)) { ExceptionUtil.handleException(request, response, new NotFoundException("Module " + getName() + " is not active in " + container.getPath()), null, false); return; diff --git a/api/src/org/labkey/api/module/Module.java b/api/src/org/labkey/api/module/Module.java index 7949dce1847..51221c02a6a 100644 --- a/api/src/org/labkey/api/module/Module.java +++ b/api/src/org/labkey/api/module/Module.java @@ -70,13 +70,11 @@ default void registerFilters(ServletContext servletCtx) {} default boolean isUnderResourcesDirectory(java.nio.file.Path path) { return false; } /** - * If true, the module is considered unreachable in any container where it's not active/enabled. Its controllers - * will not respond to requests, and it will not show in the admin menu's module list. - * - * More modules should return true, but historically we haven't been especially mindful of service-type modules + * If true, unreachable in the container. More modules should respect whether they're active in the container, + * but historically we haven't been especially mindful of service-type modules * (such as query or pipeline) being active in all containers where they're being used. */ - default boolean isAvailableOnlyWhenActive() { return false; } + default boolean isAvailable(Container container) { return true; } enum TabDisplayMode { diff --git a/api/src/org/labkey/api/view/PopupAdminView.java b/api/src/org/labkey/api/view/PopupAdminView.java index e2d2f038a13..c9e3a90e089 100644 --- a/api/src/org/labkey/api/view/PopupAdminView.java +++ b/api/src/org/labkey/api/view/PopupAdminView.java @@ -135,7 +135,7 @@ public static NavTree createNavTree(final ViewContext context) SortedSet disabledModules = new TreeSet<>(moduleComparator); disabledModules.addAll(ModuleLoader.getInstance().getModules()); disabledModules.removeAll(activeModules); - disabledModules.removeIf(Module::isAvailableOnlyWhenActive); + disabledModules.removeIf(module -> !module.isAvailable(context.getContainer())); NavTree goToModuleMenu = new NavTree("Go To Module"); Module defaultModule = null; diff --git a/mothership/src/org/labkey/mothership/MothershipModule.java b/mothership/src/org/labkey/mothership/MothershipModule.java index c47f4573207..f28ee35f357 100644 --- a/mothership/src/org/labkey/mothership/MothershipModule.java +++ b/mothership/src/org/labkey/mothership/MothershipModule.java @@ -88,9 +88,9 @@ public boolean hasScripts() } @Override - public boolean isAvailableOnlyWhenActive() + public boolean isAvailable(Container container) { - return true; + return container.isRoot() || container.getActiveModules().contains(this); } @Override